Технологии

Поиск по OCR-тексту: почему он не похож на обычный поиск

Распознанный текст полон ошибок, и точный поиск промахивается там, где человек уверен, что слово было. Разбираем типовые ошибки OCR и приёмы устойчивого поиска.

Вы точно помните, что видели на экране слово «дедлайн». Поиск по архиву скриншотов не находит ничего. Скриншот при этом есть, вы его нашли руками — и на нём действительно написано «дедлайн».

В индексе это слово записано как «депдайн». Распознавание перепутало «л» и «п», «л» и «д», и получившаяся строка не совпадает ни с чем, что вы наберёте.

Это не сбой. Это нормальный режим работы поиска по распознанному тексту, и строить его надо исходя из того, что индекс частично неверен.

#Короткий ответ

Поиск по OCR-тексту отличается от обычного тем, что в индексе лежат слова, которых никто не писал. Ошибки распознавания создают строки, отсутствующие в языке, и точное совпадение по ним не срабатывает.

Отсюда три следствия, которые определяют всю конструкцию:

  • Точный поиск даёт ложные пропуски, а не ложные срабатывания. Вы не находите то, что есть.
  • Ошибки не случайны, а систематичны: одни и те же пары символов путаются постоянно, и это можно использовать.
  • Поиск по смыслу помогает меньше, чем ожидается: искажённое слово искажает и вектор.

#Типовые ошибки распознавания

Класс Пример Почему возникает
Похожие буквы «л» → «п», «и» → «н», «б» → «в» близкие начертания в мелком кегле
Латиница вместо кириллицы «сеть» → «cеть» (латинская c) одинаковые глифы в разных алфавитах
Цифры и буквы «О» → «0», «З» → «3», «l» → «1» неотличимы во многих шрифтах
Разрыв слова «дедлайн» → «дед лайн» межбуквенный интервал принят за пробел
Склейка слов «до конца» → «доконца» узкий пробел не распознан
Пропуск строки нет текста вовсе низкий контраст, тёмная тема, антиалиасинг
Мусор из интерфейса «⌘», «×», «▾» вперемешку иконки и разделители распознаны как символы

Третья строка — самая коварная в русском тексте на скриншотах, потому что даёт невидимую глазу ошибку: строка «сеть» с латинской «c» выглядит идентично правильной и не находится никогда.

Шестая строка объясняет, почему у некоторых архив пуст: тёмная тема со сглаженным шрифтом распознаётся заметно хуже светлой. Прежде чем чинить поиск, стоит убедиться, что текст вообще снимается — как это проверяется, разобрано в справке про OCR.

#Приёмы устойчивого поиска

Шесть приёмов, от самого дешёвого к самому дорогому.

1. Префиксный поиск по каждому токену. Запрос «дедлайн» превращается в «дедлайн*». Это ничего не стоит и заодно решает проблему морфологии. От ошибок внутри слова не спасает, но спасает от искажённых окончаний — а хвост слова распознаётся хуже начала, потому что там чаще стоят засечки и знаки препинания.

2. Поиск по началу слова, а не по целому. Введите «дедл» вместо «дедлайн». Чем короче запрос, тем меньше шансов попасть на искажённый символ. Это самый эффективный ручной приём, и его стоит знать пользователю.

3. Нормализация алфавитов при индексации. Латинские глифы, совпадающие по начертанию с кириллическими, приводятся к одному алфавиту перед записью в индекс. Закрывает третью строку таблицы, которую иначе не закрыть ничем.

4. Нормализация ё. В распознанном тексте ё и е смешиваются постоянно. Полнотекстовый индекс SQLite считает их разными токенами — это проверяется на трёх строках и разобрано в гайде по FTS5. Приведение к одной букве при записи и при поиске возвращает потерянные попадания.

5. Поиск по нескольким полям сразу. Заголовок окна и название приложения распознавать не нужно — они берутся из системы и потому точны. Искать стоит по ним тоже: «Figma» найдётся по имени приложения даже там, где текст на экране распознан плохо.

6. Нечёткое сравнение. Поиск с допуском на одну-две замены символов. Самый мощный приём и самый дорогой: в полнотекстовом индексе его нет, и придётся либо перебирать, либо строить дополнительный индекс по фрагментам слов. Прежде чем идти этим путём, выжмите первые пять.

#Почему векторный поиск здесь помогает слабее обычного

Кажется логичным: раз буквальный поиск ломается на опечатках, возьмём поиск по смыслу. На практике выигрыш меньше ожидаемого.

Модель-эмбеддер кодирует «депдайн» не как «дедлайн с ошибкой», а как незнакомую строку, разбитую токенизатором на бессмысленные куски. Вектор такого фрагмента смещается, и близость к нормальному запросу падает. Помогает контекст: если вокруг искажённого слова десять правильных, вектор фрагмента останется в нужной области. Не помогает, если искажено именно ключевое слово — а ищут обычно его.

Практический вывод: на OCR-тексте векторы работают на уровне фрагмента, а не слова. Они хорошо находят «тот экран, где обсуждали сроки», и плохо — «экран, где было написано конкретно это слово». Держать оба источника всё равно полезно, но ожидания от векторной половины здесь должны быть ниже, чем на чистом тексте.

Отдельная стоимость: на распознанном тексте есть смысл индексировать не всё подряд. Экран с одним диалоговым окном и экран с двадцатью строками кода дают одинаковую строку в базе, но совершенно разную пользу при поиске.

#Как это устроено в Persona

Поиск по распознанному тексту работает на том же полнотекстовом индексе, что и остальной поиск: виртуальная таблица над таблицей скриншотов, три поля — распознанный текст, заголовок окна, имя приложения. Запрос пользователя очищается от символов, способных сломать синтаксис поискового выражения, и каждое голое слово превращается в префиксный запрос — приём номер один из списка выше применяется по умолчанию.

Поверх этого есть необязательный семантический слой на локальных ONNX-моделях: он включается переменной окружения и требует отдельной группы зависимостей, а модель скачивается при первом обращении. Без него поиск работает полнотекстовый, и это нормальный режим.

Слияние двух источников здесь сделано проще, чем в памяти чата: к обратному рангу полнотекстовой выдачи прибавляется косинусная близость. На коротких списках это работает, но полагаться на такое сложение неправильно: шкалы у двух источников разные, и их совпадение по порядку величины — случайность, а не свойство.

Отдельно про приватность: распознанный текст перед записью в базу и в индекс прогоняется через правила маскирования — регулярные выражения, заменяющие совпадения на маску. Готовые наборы правил покрывают карты, пароли, ключи API, сетевые идентификаторы и контакты; на изображение это не влияет, маскируется только текстовое представление. Практическое следствие для поиска: замаскированное не находится, и это правильно. Как настраивать правила — в материале про правила редактирования OCR.

Захват экрана и всё производное от него, включая этот поиск, доступны только владельцу инстанса: данные захвата пока глобальные, одно-тенантные. Зачем вообще снимать экран в память ассистента, разобрано в статье про запись экрана как источник памяти ИИ.

#Где интуиция подводит

Кажется, что качество распознавания — вопрос движка. Оно в большей степени вопрос исходной картинки: масштаб интерфейса, тема оформления, сглаживание шрифтов и разрешение влияют сильнее, чем выбор движка. Проверить дёшево: снимите один и тот же экран в светлой и тёмной теме и сравните распознанный текст.

Кажется, что «ничего не находится» — проблема поиска. В половине случаев это проблема входа: текста в индексе нет вовсе. Проверяется не поиском, а просмотром распознанного текста конкретного скриншота.

Кажется, что модель зрения решает всё. Она читает изображение лучше классического распознавания, но стоит несопоставимо дороже: применять её ко всему потоку скриншотов нереалистично, а к отдельному — оправдано. Что умеют такие модели локально, разобрано в материале про локальные vision-модели.

#Частые вопросы

#Почему поиск не находит слово, которое я вижу на скриншоте?

Скорее всего, в индексе оно записано с ошибкой распознавания. Проверьте прямо: откройте распознанный текст этого скриншота и посмотрите, как слово там выглядит. Если оно искажено — ищите по короткому началу слова.

#Помогает ли увеличить разрешение съёмки?

Помогает до определённого предела, и упирается всё в размер шрифта в пикселях, а не в разрешение экрана. Увеличение масштаба интерфейса даёт заметно больше, чем увеличение разрешения при том же масштабе.

#Стоит ли исправлять ошибки распознавания вручную?

Для отдельных важных экранов — да, если инструмент это позволяет: одна правка возвращает документ в поиск навсегда. Массово — бессмысленно, объёмы не те.

#Почему нельзя просто искать с опечатками?

Нечёткий поиск требует либо перебора, либо дополнительного индекса по фрагментам слов, а во встроенном полнотекстовом поиске его нет. Это выполнимо, но это отдельная подсистема, и начинать стоит с более дешёвых приёмов.

#Как это связано с памятью ассистента?

Распознанный текст — один из источников, из которых память может пополняться. Важная оговорка: текст с экрана не атрибутирован говорящему, поэтому в аккуратной схеме он не может сам по себе создать факт о человеке — только поддержать факт, у которого есть другое доказательство. Общее устройство слоёв разобрано в статье о том, как работает память у ИИ.

#Вывод

Стройте поиск по распознанному тексту исходя из того, что индекс частично неверен: включайте префиксный поиск по умолчанию, нормализуйте алфавиты и ё при записи, ищите заодно по заголовку окна и имени приложения. Пользователю дайте простое правило: вводить начало слова, а не слово целиком. И не рассчитывайте, что векторы закроют ошибки распознавания, — они работают на уровне фрагмента, а ищут обычно конкретное слово, которое как раз и искажено.

Хотите ИИ, который помнит вас?

Persona держит контекст между чатами и работает на вашем железе — если развернуть её у себя и подключить локальную модель.

Создать аккаунт