Перейти к содержимому
Поиск
Разработка

Tesseract.js: распознавание текста — OCR-мастерская и 8 примеров

Tesseract.js на практике: распознаём русский и английский текст, выделяем область, готовим изображения, обрабатываем очередь и страницу PDF. Восемь живых сценариев, редактирование, TXT/JSON и исходники для Windows и Linux.

Александр Некрасов
Tesseract.js — распознавание текста с изображений

Tesseract.js на практике: распознаём русский и английский текст, выделяем область, готовим изображения, обрабатываем очередь и страницу PDF. Восемь живых сценариев, редактирование, TXT/JSON и исходники для Windows и Linux.

Что такое Tesseract.js и какой результат вы получите

На скриншоте есть полезный текст, но выделить его мышью нельзя. В личный кабинет загрузили скан заявки, а оператор снова перепечатывает описание. В папке накопились изображения с короткими кодами, которые нужно перенести в таблицу. Tesseract.js помогает превратить пиксели в обычный текст, пригодный для редактирования, поиска и дальнейшей обработки. Это OCR — оптическое распознавание символов.

В этой статье мы соберём рабочую цепочку: выбрать изображение, указать язык и нужную область, подготовить картинку, увидеть прогресс, проверить результат и скачать TXT или JSON. Здесь восемь живых сценариев, собственные учебные файлы, браузерная мини-мастерская и Node.js CLI. Можно сразу попробовать распознавание на странице, затем забрать весь проект и использовать его в своём интерфейсе.

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

Где применять: кабинет, документы, поддержка и внутренние инструменты

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

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

Выбор браузера или сервера зависит от задачи. В браузере вычисления выполняются на устройстве посетителя и наш код не отправляет картинку в OCR-сервис. На Node.js удобно обрабатывать файлы по расписанию, интегрировать результат с очередью и хранить историю выполнения. Один и тот же пользовательский сценарий может начинаться в браузере, а большие пакеты уходить в отдельный серверный процесс с контролем нагрузки.

Как работает OCR: картинка, Worker, языки и результат

Страница передаёт подготовленное изображение библиотеке, библиотека запускает движок Tesseract через WebAssembly. Языковые данные помогают распознать символы и слова выбранного языка. Вычислительная работа вынесена в Worker, поэтому интерфейс может показывать прогресс и принимать команду остановки. Отдельно работают наши элементы управления, Canvas-подготовка и экспорт; это код мастерской, а не готовый виджет Tesseract.

Для одного файла достаточно создать worker, задать параметры, вызвать recognize и освободить ресурсы. Для нескольких последовательных файлов worker лучше использовать повторно: загрузка движка и языка не нужна перед каждой картинкой. В исходниках этот жизненный цикл виден целиком. В браузере над OCR-worker стоит небольшой управляющий worker: он позволяет оборвать задачу, включая подготовку движка, и не принимать поздний ответ от старого запуска.

const worker = await Tesseract.createWorker('rus');
try {
  const { data } = await worker.recognize(imageFile);
  console.log(data.text);
} finally {
  await worker.terminate();
}

Этот короткий пример показывает API; без локальных путей библиотека может использовать стандартные удалённые адреса. В полной мастерской все пути заданы явно и все библиотеки лежат рядом с проектом. Устройство API описано в официальном проекте Tesseract.js.

Версии и состав воспроизводимого проекта

Примеры закреплены на Tesseract.js 7.0.0 и tesseract.js-core 7.0.0. Русские и английские языковые пакеты имеют версию 1.0.0; используется их набор 4.0.0_best_int. Для страницы сканированного PDF подключён PDF.js 6.5.438, оформление выполнено на Bootstrap 5.3.8. Эти номера относятся к разным компонентам и не должны совпадать между собой.

В архиве находится package-lock.json. Команда npm ci устанавливает именно зафиксированный набор, а сборка копирует runtime-файлы в public. Внутри public нет ссылок на CDN с плавающим latest. Node.js проект требует версию не ниже 22.16 из-за общего набора зависимостей; для нового окружения используйте поддерживаемую ветку Node.js 24. Это требование данного проекта, а не утверждение о минимуме каждой отдельной библиотеки.

npm ci --ignore-scripts
npm run build
npm test
npm run verify
npm audit --omit=dev

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

Мини-мастерская: восемь задач, которые можно попробовать на странице

Начните с русского образца: нажмите «Распознать», дождитесь завершения и сравните текст с картинкой. Затем исправьте любое слово в поле результата и скачайте TXT и JSON. Следующий полезный эксперимент — сменить язык смешанного образца, изменить область либо отключить порог у бледного изображения. Так вы увидите связь между исходником, настройками и качеством результата.

Каждый режим содержит цель и конкретное действие. Доступны свои PNG, JPEG, WebP, а также отдельный PDF. Очередь обрабатывает до трёх картинок последовательно; итоговый экспорт включается после завершения всего пакета. Когда вы меняете вход или настройки, старый результат очищается и не выдаётся за новый. Кнопка сброса возвращает файлы и параметры выбранного учебного сценария.

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

Windows: скачать проект и запустить без отдельного сервера OCR

Установите поддерживаемый Node.js с официального сайта, распакуйте архив и откройте PowerShell в каталоге проекта. В командах используется npm.cmd: так запуск не зависит от политики выполнения PowerShell-скриптов npm.ps1. Если путь содержит пробелы, заключите его в кавычки. Никаких паролей, внешних OCR-ключей и регистрации для этого проекта не требуется.

Set-Location 'C:/Projects/tesseract-project'
node --version
npm.cmd ci --ignore-scripts
npm.cmd run build
npm.cmd test
npm.cmd run verify
npm.cmd run serve

Откройте http://127.0.0.1:8186/ в браузере. Сервер разработки слушает только локальный адрес и раздаёт статические файлы; распознавание выполняется в браузере. Для остановки сервера нажмите Ctrl+C. Двойной щелчок по index.html не заменяет HTTP: Worker и модульные импорты имеют ограничения для file://, из-за которых исправный код может не загрузиться.

Для распознавания одного файла через консоль откройте второй терминал. Результат будет записан в новый JSON-файл UTF-8. Если такой файл уже существует, CLI остановится с ошибкой вместо молчаливой перезаписи. Это удобно при повторных экспериментах: разные настройки сохраняются под разными именами и их можно сравнить.

node bin/ocr.mjs "fixtures/ru.png" rus "result-ru.json"

Linux и VPS: реальные команды для работы с файлами

На Linux используется тот же исходный проект и тот же lock-файл. Установите поддерживаемый Node.js подходящим для дистрибутива способом, проверьте node –version, распакуйте архив в каталог обычного пользователя. Не запускайте обработку пользовательских документов от root и не складывайте результаты в публичный каталог сайта: распознанный текст часто содержит больше доступной для поиска информации, чем исходная картинка.

cd ~/projects/tesseract-project
node --version
npm ci --ignore-scripts
npm run build
npm test
npm run verify
umask 077
mkdir -p results
node bin/ocr.mjs fixtures/ru.png rus results/first.json

Команда umask задаёт ограниченные права для создаваемых файлов текущего процесса. У пользователя должны быть права чтения исходников и записи результата. Если обработку запускает планировщик, задавайте полный путь к Node, проекту и файлам: окружение cron может отличаться от интерактивного SSH. Проверяйте код завершения и сохраняйте журнал ошибок отдельно от результата.

Для просмотра браузерной версии на VPS можно оставить локальный сервер на 127.0.0.1 и подключиться через SSH-туннель с рабочего компьютера. Это полезно для проверки сборки до публикации. Для постоянной публичной мастерской отдельный Node-процесс не нужен — достаточно статической раздачи каталога public через HTTPS.

ssh -L 8186:127.0.0.1:8186 user@your-vps

Локальные языки и ядро: что загружается при первом запуске

В проекте разделены три адреса: workerPath указывает на Worker библиотеки, corePath — на каталог вариантов ядра, langPath — на языковые файлы. Сборка сохраняет шесть вариантов wasm.js из core 7.0.0, чтобы библиотека выбрала подходящий для устройства. В этих файлах находится соответствующий WebAssembly-runtime; копирование только случайно выбранного SIMD-файла ухудшит совместимость.

const base = new URL('./vendor/', location.href);
const worker = await Tesseract.createWorker('rus+eng', 1, {
  workerPath: new URL('worker.min.js', base).href,
  corePath: new URL('core/', base).href,
  langPath: new URL('lang', base).href,
  workerBlobURL: false,
  cacheMethod: 'none',
  logger: message => console.log(message.status, message.progress)
});

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

Если интерфейс завис на загрузке, сначала посмотрите сеть: нужен успешный ответ на worker, ядро и traineddata.gz. Сервер должен отдавать JavaScript с правильным MIME. Языковой gzip является файлом для библиотеки; не присваивайте ему ошибочный Content-Encoding, заставляющий сервер или браузер распаковать его не в том месте. Подробнее: локальное размещение Tesseract.js.

Пример 1. Русский текст: от заявки к редактируемому полю

В первом образце — короткая печатная заявка на обслуживание. Представьте экран CRM, где рядом стоят изображение документа и поле описания. Кнопка распознавания заполняет черновик, но пользователь видит оригинал и может исправить ошибку до сохранения. Такой интерфейс экономит перепечатывание, не скрывая источник данных.

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

const worker = await Tesseract.createWorker('rus');
try {
  await worker.setParameters({ tessedit_pageseg_mode: '6' });
  const { data } = await worker.recognize(file);
  document.querySelector('#description').value = data.text;
} finally {
  await worker.terminate();
}

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

Примеры 2 и 3. Английский документ и смешанный RU/EN

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

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

const language = form.language.value; // rus, eng или rus+eng
if (!['rus', 'eng', 'rus+eng'].includes(language)) {
  throw new Error('Неподдерживаемый язык');
}
const worker = await Tesseract.createWorker(language);

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

Пример 4. Распознавание области без соседних блоков

На скриншоте интерфейса обычно есть меню, подписи, уведомления и нужный человеку центральный фрагмент. Если распознать всё, результат смешает эти элементы. В режиме области учебное изображение содержит служебный заголовок, полезные строки и нижнюю подпись. Заданная рамка оставляет только нужные строки; поменяйте её размеры, чтобы увидеть разницу.

В мастерской координаты задаются в пикселях исходника: X, Y, ширина и высота. Они не зависят от того, насколько уменьшена картинка на телефоне. Значения должны быть целыми, ширина и высота положительными, а прямоугольник полностью находиться внутри изображения. Синяя рамка рисуется только для человека и не включается в изображение, передаваемое движку.

const rectangle = { left: 40, top: 135, width: 1000, height: 180 };
const { data } = await worker.recognize(image, { rectangle });
console.log(data.text);

Это API-способ ограничить исходник. В полной мастерской область сначала вырезается через Canvas, потому что затем к ней применяются поворот, масштаб и порог. Не обрезайте одну и ту же область второй раз в координатах исходной картинки: после Canvas-подготовки её система координат уже другая. Для очереди наша ручная область относится к первой картинке; остальные обрабатываются целиком, что прямо следует из кода.

Пример 5. Подготовка изображения: масштаб, поворот и порог

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

const pixels = context.getImageData(0, 0, width, height);
for (let i = 0; i < pixels.data.length; i += 4) {
  const a = pixels.data;
  const gray = 0.2126 * a[i] + 0.7152 * a[i + 1] + 0.0722 * a[i + 2];
  const value = gray < 170 ? 0 : 255;
  a[i] = a[i + 1] = a[i + 2] = value;
  a[i + 3] = 255;
}
context.putImageData(pixels, 0, 0);

Этот простой порог полезен для демонстрации, но не устраняет перспективу, тени и размытие автоматически. Если тонкие штрихи исчезли, верните исходник. Масштаб 2× в четыре раза увеличивает число пикселей, поэтому расход памяти растёт быстрее видимого размера. Для большого снимка разумнее сначала выделить нужный блок.

Наш интерфейс поддерживает поворот на 90, 180 и 270 градусов; автоматического выравнивания наклонённых строк здесь нет. Такие операции и более сложную подготовку можно вынести в отдельный этап. Смотрите официальные рекомендации по качеству изображения и материал сайта о Sharp для обработки изображений на Node.js.

Пример 6. Цифровой код и ограниченный алфавит

Шестой пример содержит одну строку цифр. Такой сценарий встречается при переносе внутреннего кода с фотографии или скана наклейки. Мы выбираем PSM 7, соответствующий одной строке, и разрешаем только цифры. Сравните результат с исходником, затем очистите whitelist или смените режим: это наглядно показывает, что настройки должны соответствовать конкретной задаче.

await worker.setParameters({
  tessedit_pageseg_mode: '7',
  tessedit_char_whitelist: '0123456789'
});
const { data } = await worker.recognize(codeImage);
const candidate = data.text.trim();
if (!/^\d{8}$/.test(candidate)) {
  throw new Error('Нужна ручная проверка восьмизначного кода');
}

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

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

await worker.setParameters({ tessedit_pageseg_mode: '6', tessedit_char_whitelist: '' });

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

PSM описывает ожидаемую структуру текста. В интерфейсе доступны автоматическая сегментация без OSD, единый блок, одна строка и разрозненный текст. Не используйте один режим механически для страницы книги, короткого кода и скриншота панели. Начните с соответствующего расположению текста варианта, затем сравните его с ближайшей альтернативой.

PSM Когда пробовать Что проверить
3 Страница с несколькими блоками Порядок чтения и разделение колонок
6 Один печатный блок Не объединяются ли соседние элементы
7 Одна строка кода или подписи Область не содержит второй строки
11 Разрозненные подписи Результат не гарантирует нужный порядок

Если движок успешно обработал пустую область, data.text может быть пустым. Это отличается от ошибки загрузки языка или запуска Worker. В интерфейсе полезно различать эти ситуации: «Текст не найден, проверьте область» и «Не удалось загрузить компонент» ведут к разным действиям. Не заменяйте любую пустую строку фиктивным успешным результатом.

const text = data.text.trim();
if (text.length === 0) {
  showMessage('Текст не найден: проверьте область, язык и изображение.');
} else {
  editor.value = data.text;
}

Пример 7. Очередь изображений: один Worker и понятный прогресс

Очередь объединяет русский, английский и смешанный образцы. Пользователь видит номер обрабатываемого файла, промежуточный результат и общий ход работы. Такая схема подходит для небольшого пакета приложений к заявке. Один worker обрабатывает картинки последовательно; создание нескольких параллельных движков здесь лишь увеличило бы память и усложнило поведение на телефоне.

const worker = await Tesseract.createWorker('rus+eng');
try {
  for (const file of files) {
    const { data } = await worker.recognize(file);
    results.push({ name: file.name, text: data.text });
  }
} finally {
  await worker.terminate();
}

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

Для промышленного потока измерьте время и память на своих документах. Количество workers выбирают по доступным ресурсам, а не по числу входящих HTTP-запросов. Очередь задач позволяет ограничить параллелизм, повторять неудачные операции и отслеживать прогресс. На сайте есть отдельная практическая статья о BullMQ для фоновых задач Node.js.

Остановка, таймер и защита от результата старого запуска

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

let revision = 0;
function cancel() {
  revision += 1;
  controller?.terminate();
  controller = null;
}
const currentRevision = revision;
controller.onmessage = event => {
  if (currentRevision !== revision) return;
  renderProgress(event.data);
};

В учебном интерфейсе OCR ограничен двумя минутами. Это не предел библиотеки: это выбранная граница одной демонстрационной задачи. Подготовка файла и отрисовка PDF происходят раньше и не входят в этот таймер. При остановке по времени сообщение предлагает уменьшить область, размер или пакет; нельзя молча показывать 100% и пустой текст.

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

Пример 8. Скан PDF: PDF.js → Canvas → Tesseract

Tesseract.js не принимает PDF как готовый документ для распознавания. В восьмом режиме отдельная библиотека PDF.js читает PDF и рисует выбранную страницу на Canvas. Только полученное изображение попадает в OCR. В архиве лежит настоящий одностраничный PDF, содержащий растровый скан, поэтому извлечение готового текстового слоя не подменяет распознавание.

const task = pdfjsLib.getDocument({ data: new Uint8Array(pdfBytes) });
try {
  const pdf = await task.promise;
  const page = await pdf.getPage(1);
  const viewport = page.getViewport({ scale: 1.5 });
  canvas.width = Math.ceil(viewport.width);
  canvas.height = Math.ceil(viewport.height);
  await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;
  const { data } = await worker.recognize(canvas);
  console.log(data.text);
} finally {
  await task.destroy();
}

В нашей версии PDF.js ресурсы освобождаются через объект загрузки task. Для пользовательского PDF выберите номер и нажмите «Показать страницу», затем запускайте распознавание. Изменение номера без отрисовки блокирует запуск, чтобы результат старого изображения не был подписан номером новой страницы. Мастерская обрабатывает одну выбранную страницу, а не незаметно весь документ.

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

Ручная правка и экспорт: исходный текст должен сохраняться

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

const item = {
  text: ocrText,
  correctedText: editor.value,
  manuallyEdited: editor.value !== ocrText,
  confidence: data.confidence
};
const json = JSON.stringify({ engine: 'Tesseract.js 7.0.0', results: [item] }, null, 2);

В JSON мастерской есть параметры запуска, исходный text, correctedText и признак ручного изменения. TXT предназначен для непосредственной работы и содержит исправленную версию. Отдельно скачиваются настройки; это помогает повторить удачный вариант подготовки. Координаты слов относятся к исходному OCR и не перестраиваются после ручного редактирования, что нужно учитывать при подсветке исправленного текста.

Не используйте экспорт как скрытое сохранение персональных документов на сервере. В данной реализации скачивание создаёт Blob в браузере, а выбранные данные живут в памяти страницы. Если вы добавляете собственное хранение, заранее определите срок удаления, права доступа и понятное действие пользователя «Сохранить в заявку». Это уже поведение вашего сервиса, требующее отдельной реализации.

Confidence и координаты: подсказки для проверки, а не доказательство

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

Подробный вывод blocks включается явно. Из вложенных блоков, абзацев и строк можно получить слова с координатами. Это позволяет построить интерфейс, где нажатие на слово подсвечивает соответствующую область изображения. Для нашей компактной мастерской координаты доступны в раскрываемом JSON; визуальное редактирование геометрии не имитируется.

const { data } = await worker.recognize(image, {}, {
  text: true,
  blocks: true
});
const words = (data.blocks ?? []).flatMap(block =>
  (block.paragraphs ?? []).flatMap(paragraph =>
    (paragraph.lines ?? []).flatMap(line => line.words ?? [])
  )
);

После обрезки, масштабирования и поворота координаты относятся к подготовленному изображению. Для подсветки на оригинале потребуется обратное преобразование. Не умножайте значения на случайный CSS-коэффициент, забыв про смещение области и поворот. API-параметры и структура результатов описаны в справочнике Tesseract.js.

Node.js CLI: один файл, папка и повторяемый результат

Готовый bin/ocr.mjs принимает путь к файлу или папке, язык и имя выходного JSON. Папка просматривается без рекурсии, файлы сортируются, неподходящие расширения отбрасываются. CLI ограничивает небольшой пакет двадцатью изображениями по восемь МиБ и использует один worker. PDF напрямую не принимается: это явно отличает интерфейс командной строки от браузерного режима с PDF.js.

node bin/ocr.mjs fixtures/eng.png eng result-en.json
node bin/ocr.mjs "./input-scans" rus+eng results/day-01.json

Для использования из другого Node-модуля экспортирована функция recognizeFiles. Можно передать разрешённый PSM и whitelist, получить массив результатов и сохранить его своим способом. Она освобождает worker в finally. При большом или недоверенном входе нужны дополнительные пределы распакованного изображения, времени и памяти процесса: небольшой локальный CLI не является готовым публичным сервисом загрузки.

import { recognizeFiles } from './bin/ocr.mjs';
const results = await recognizeFiles(['fixtures/digits.png'], {
  langs: 'eng', psm: '7', whitelist: '0123456789'
});
console.log(results[0].text);

Храните параметры вместе с результатом и не заменяйте существующий файл незаметно. Для периодического задания удобно формировать имя из ID пакета, записывать начало и завершение в журнал, а ошибки отправлять в канал наблюдения. В логах обычно достаточно ID и технического состояния: весь распознанный документ туда копировать не нужно.

Как встроить OCR в PHP-приложение или Node.js сервис

В простом кабинете браузерная мастерская может заполнить форму, а обычный PHP-обработчик сохранит подтверждённый текст и ссылку на исходный документ. Так сервер вообще не выполняет OCR. Если нужен автоматический массовый процесс, PHP сохраняет загрузку вне web-root и создаёт задание; отдельный Node-worker читает файл, распознаёт его и записывает результат. Интерфейс получает состояние по ID задания.

Для такой интеграции полезен явный контракт состояний: queued, processing, review, done, failed. Статус review означает, что вычисление закончилось, но данные требуют проверки. Повторная доставка задания не должна создавать вторую карточку: сохраняйте уникальный ID операции и отделяйте вычисление от окончательного применения результата к бизнес-объекту.

{
  "jobId": "ocr-demo-104",
  "status": "review",
  "language": "rus+eng",
  "sourceId": "upload-104",
  "resultId": "text-104",
  "requiresHumanReview": true
}

Это пример контракта, а не запущенный на сайте сервер обработки загрузок. В исходниках нет публичного upload-endpoint. Его реализация требует проверки прав, формата, размера, количества задач и доступа к результатам. Для наблюдения за очередью пригодится журнал структурированных событий; на сайте разобран Pino для Node.js.

Linux/VPS: публикуем браузерную мастерскую через HTTPS

После npm run build для браузерной версии нужен только каталог public. Перенесите его в отдельный каталог статического сервера, сохранив vendor, fixtures и source. Не публикуйте node_modules, входящие серверные документы, результаты CLI и приватные настройки. Сервер разработки из архива остаётся инструментом локальной проверки, а не демоном для постоянной эксплуатации.

ocr.example.com {
    root * /srv/ocr/public
    encode zstd gzip
    file_server
    header X-Content-Type-Options nosniff
}

Это пример отдельного Caddy-хоста: замените домен и каталог, настройте DNS и выполните требования Caddy для выдачи HTTPS. Если мастерская добавляется в существующий сайт, используйте отдельный маршрут и проверьте относительные пути на фактическом URL. Подробности сервера разобраны в статье о Caddy, HTTPS и reverse proxy.

После размещения откройте мастерскую без административной сессии, проверьте загрузку локального языка, распознавание, PDF и скачивание архива. Отдельно проверьте MIME JavaScript: публичные ES-модули проекта имеют расширение .js, поскольку не каждый хостинг правильно отдаёт .mjs. Исходный CLI доступен для чтения как ocr.mjs.txt, а в ZIP остаётся исполняемым модулем .mjs.

Ошибки загрузки, памяти и результата: как искать причину

Если worker не стартует, сначала проверьте HTTP-статусы и MIME, затем CSP и фактические пути. Если движок загрузился, но язык нет, проверьте имя traineddata.gz и содержимое ответа: вместо данных сервер мог вернуть HTML-страницу ошибки. Если проблема возникает только на большом фото, уменьшите область и масштаб; число пикселей важнее одного размера сжатого файла.

Если текст неверный, проверяйте по очереди язык, область, ориентацию, PSM и подготовку. Не начинайте со случайного изменения десятков параметров. Сохраните исходник, настройки и ответ до правки, затем сравните один изменённый фактор. Для пустого результата полезно убедиться, что нужные буквы вообще видны на подготовленном Canvas.

try {
  await recognizeAndRender();
} catch (error) {
  status.textContent = 'Не удалось обработать файл: ' + error.message;
  downloadButton.disabled = true;
} finally {
  runButton.disabled = false;
}

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

Мобильный интерфейс и ресурсные границы

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

В нашей мастерской задано до трёх файлов, восемь МиБ на файл и двенадцать миллионов пикселей для исходного и подготовленного изображения. PDF ограничен пятьюдесятью страницами, при этом рисуется только выбранная. Эти пределы защищают учебный интерфейс от случайно огромного задания; они не означают, что любой подходящий по размеру документ гарантированно будет быстро обработан на каждом телефоне.

Для собственного продукта измерьте реальные сценарии на целевых устройствах. Уменьшайте область до нужного блока, не включайте 2× без причины и показывайте возможность отмены. Скачивание должно работать обычной ссылкой или пользовательским действием, а не зависеть от всплывающего окна. Успешная проверка узкого viewport не заменяет проверку физического телефона, особенно при больших файлах и нехватке памяти.

Что входит в исходники и как проверить сборку

Архив содержит полный HTML/CSS/JavaScript интерфейс, управляющий OCR-worker, Node.js CLI, шесть собственных изображений и скан PDF, manifest с ожидаемым текстом и SHA-256, тесты и инструкцию Windows/Linux. Сборка воспроизводимо копирует локальные зависимости; исходные тексты доступны рядом с готовой мастерской. Полные лицензии библиотек сохранены отдельно от MIT-лицензии нашего кода.

src/                 # интерфейс, сценарии, Worker и экспорт
bin/ocr.mjs          # CLI и функция recognizeFiles
fixtures/            # учебные изображения, PDF, manifest
public/              # готовая статическая мастерская
tests/ocr.test.mjs    # настоящее OCR и проверка контрактов
tools/build.mjs      # локальная сборка runtime-ассетов
README.md            # Windows, Linux, VPS и ограничения

Проверка включает реальное распознавание русского, английского и смешанного текста, область без заголовка и подписи, цифровую строку, сброс whitelist и цепочку PDF.js → Canvas → OCR. Отдельно проверяются неправильные параметры и сохранность исходного текста при ручной правке. Это полезнее сравнения интерфейса с фиксированной строкой, потому что тест действительно запускает движок.

После изменений запускайте тесты, build и verify, а затем проверяйте браузерные действия: смену режима, отмену, ошибку файла, редактирование, TXT/JSON и работу на узкой ширине. Статический тест не доказывает корректность MIME на конкретном хостинге или доступность файла из iframe. Поэтому локальная сборка и приёмка опубликованной страницы — разные этапы.

Когда выбрать другой инструмент и что читать дальше

Tesseract.js подходит, когда нужен доступный OCR печатного текста с понятным локальным конвейером и контролем исходников. Он не обещает универсального понимания произвольного почерка, восстановления сложных таблиц или готового разбора всех реквизитов документа. Для специализированных форм сравнивайте инструменты на собственном размеченном наборе и учитывайте стоимость проверки ошибок, а не только скорость демонстрации.

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