Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

Распознавание документов в браузере с оценкой качества изображения: пошаговая интеграция

Дата публикации: 26-08-2026 15:18:00

Как интегрировать распознавание документов в браузере через WebAssembly с оценкой качества изображения. Пошаговый гайд по SDK Smart Engines для KYC.— Читать дальше «Распознавание документов в браузере с оценкой качества изображения: пошаговая интеграция»

Основное содержимое страницы с новостью.

Браузеры стали ключевым каналом для удалённой идентификации: цифрового онбординга, KYC и похожих сценариев, где требуется подтвердить личность по документу. В таких сценариях пользователь сам распознает документ с помощью мобильного устройства. Отдельная задача внутри этого процесса – оценка качества изображения: если палец, блик или посторонний предмет перекрыли значимую часть документа, система сразу подсказывает пользователю, что именно мешает пройти проверку KYC. Кроме того, оценка качества изображения позволяет защититься от атак на предъявление (PAD) – когда реквизиты или признаки подлинности документа оказываются перекрыты намеренно.

Ниже рассмотрим два кейса: распознавание паспорта в видеопотоке и распознавание свидетельства о рождении по фотографии.

Оценка качества изображения

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

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

Законодательство РФ (включая 115-ФЗ) устанавливает конкретные требования к идентификации личности. Поэтому в случае с распознаванием недостаточно извлечь отдельные поля документа – нужно убедиться, что в кадре присутствуют все данные и элементы, необходимые для проверки. Контроль качества изображения в процессе распознавания документов решает эту задачу вместе с ещё одной: защищает от мошеннических атак.

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

Распознавание в браузере с помощью WebAssembly

Smart ID Engine может работать в браузере в виде WebAssembly-модуля. Это позволяет выполнять распознавание непосредственно на стороне клиента, не отправляя изображение документа на сервер.

Браузеры предоставляют следующие фичи в зависимости от возможностей устройства:

  • Single Instruction Multiple Data (SIMD) – использование векторных инструкций процессора, которые позволяют значительно увеличить скорость вычислений.
  • Threads – выполнение вычислений параллельно.

Проверить поддержку SIMD-инструкций и многопоточности в браузере можно с помощью библиотеки wasm-feature-detect.

Smart Engines предоставляет набор библиотек для всех комбинаций этих фич:

  • nosimd.nothreads – базовая сборка, поддерживаемая везде.
  • simd.nothreads – самая быстрая однопоточная сборка, поддерживается почти на всех современных устройствах (Safari 17.1+, Chrome 91+, Firefox 89+); часто является оптимальным выбором.
  • simd.threads – сборка с поддержкой многопоточности; требует дополнительных настроек на сервере, но позволяет добиться максимальной производительности.
  • nosimd.threads – редкая комбинация, она здесь на всякий случай.
Настройка веб-сервера

До подключения библиотеки к веб-приложению необходимо подготовить сервер, с которого браузер будет получать WASM-модули.

Первые два пункта являются строго обязательными для нормального функционирования библиотеки в браузере.

  • Content-Type. Веб-сервер должен возвращать Content-Type: application/wasm для файлов с расширением *.wasm.
  • Сжатие. Веб-сервер должен поддерживать компрессию файлов c расширением .wasm. WASM-файлы хорошо сжимаются и это значительно позволит сократить время доставки файлов до клиента. Убедитесь в наличии заголовка content-encoding у файла *.wasm при отдаче сервером. Рекомендуется отдавать wasm-файлы уже сжатыми, чтобы не нагружать сервер сжатием файлов на лету на каждый запрос.
  • Поддержка CSP. Если у вас настроена строгая политика безопасности контента (CSP), она заблокирует JIT-компиляцию WebAssembly для защиты от XSS-атак. Для того, чтобы ее включить, необходимо добавить директиву wasm-unsafe-eval. Она разрешает выполнение только WebAssembly и в отличие от unsafe-eval по-прежнему блокирует вызовы JavaScript eval() и new Function(). Без этой директивы загрузка или инстанцирование (создание экземпляра) WASM-модуля будет невозможно.
  • Многопоточность. Для использования многопоточной сборки браузеру дополнительно требуется обеспечить изоляцию контекста (Cross-Origin Isolation), настроив на сервере заголовки Cross-Origin-Opener-Policy и Cross-Origin-Embedder-Policy.

Например, в nginx это можно сделать, дописав в конфигурацию:

			add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;

		
КамераШирокоугольная камера

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

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

Разрешение

Существует распространенное представление о том, что для качественного распознавания изображение необходимо подавать в максимально возможном разрешении. Этот подход был оправдан для алгоритмов предыдущего поколения. Современные системы распознавания зависят от исходного разрешения гораздо меньше. По возможности лучше всегда избегать работы с большим объемом данных, поскольку это так или иначе добавляет накладные расходы на пересылку UI-> Worker->VM WASM.

Оценим необходимое разрешение для случая оценки качества. Для уверенного распознавания и проверки подлинности документа необходимо обеспечить не менее 120 DPI. Физический размер страницы паспорта составляет 88 × 125 мм. Будем считать, что разворот паспорта предъявляется наиболее естественным образом – вертикально. Это означает, что изображение паспорта должно иметь разрешение не менее 591 на 832 пикселей. Паспорт занимает не весь кадр; если он занимает 0.8 по ширине и высоте (около 65% от площади кадра), то необходимое разрешение получится 739 на 1040. Разумеется, лучше обеспечить некоторый запас по DPI и предусмотреть погрешности предъявления. При этом для съемки паспорта РФ лучше подходит формат кадра с соотношением сторон 4:3.

У свидетельства о рождении в формате А4 физический размер 210 × 297 мм или 8,27 × 11,69 дюйма. Если считать, что оно занимает по 0.9 кадра по каждой из сторон, необходимо разрешение 1102 на 1559. Тут также лучше подходит формат кадра 4:3.

В результате, оптимальным будет разрешение кадра 1600 на 1200 в вертикальной ориентации.

Отметим, что как правило у веб-камер даже сравнительно современных ноутбуков разрешение ограничено 720 на 1280 px, что ограничивает распознавание документов А4, таких как свидетельство о рождении. Дополнительно ситуацию может ухудшить отсутствие автофокуса.

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

Пример работы с камерой

Открываем камеру для съемки видео:

			stream = await navigator.mediaDevices
 .getUserMedia({ video: { facingMode: { ideal: "environment" } } })

		

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

			let stream
let cameraAnimationFrame

const LANDSCAPE = {
 width: 1600,
 height: 1200,
}

const PORTRAIT = {
 width: 1200,
 height: 1600,
}

function isPortraitScreen() 
{
 return window.matchMedia("(orientation: portrait)").matches
}

function buildCameraConstraints(cameraId, useExactResolution = true) 
{
 const resolution = useExactResolution ? {
   width: { exact: LANDSCAPE.width },
   height: { exact: LANDSCAPE.height },
 } : {
   width: { ideal: LANDSCAPE.width },
   height: { ideal: LANDSCAPE.height },
 }
 const videoConstraints = {
   ...resolution,
   aspectRatio: { ideal: LANDSCAPE.width / LANDSCAPE.height },
 }

 if (cameraId) {
   videoConstraints.deviceId = { exact: cameraId }
 } else {
   videoConstraints.facingMode = { ideal: "environment" }
 }
 return {
   audio: false,
   video: videoConstraints,
 }
}

function resizeCanvasesForCamera(videoWidth, videoHeight) {
 const usePortraitCanvas = isPortraitScreen() && videoWidth > videoHeight
 const target = usePortraitCanvas ? PORTRAIT : { width: videoWidth, height: videoHeight }
 canvas.width = overlayCanvas.width = target.width
 canvas.height = overlayCanvas.height = target.height
 return { usePortraitCanvas }
}

function drawCameraFrame(ctx, usePortraitCanvas) {
 ctx.save()
 ctx.clearRect(0, 0, canvas.width, canvas.height)
 if (usePortraitCanvas && video.videoWidth > video.videoHeight) {
   ctx.translate(canvas.width, 0)
   ctx.rotate(Math.PI / 2)
   ctx.drawImage(video, 0, 0, canvas.height, canvas.width)
 } else {
   ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
 }
 ctx.restore()
}

async function cameraInit(cameraId) {
 const constraints = buildCameraConstraints(cameraId, true)
 try {
   // Remove all streams before switching devices
   if (cameraAnimationFrame) {
     cancelAnimationFrame(cameraAnimationFrame)
   }
   if (stream) {
     stream.getTracks().forEach((track) => track.stop())
   }
   try {
     stream = await navigator.mediaDevices.getUserMedia(constraints)
   } catch (error) {
     if (error.name !== "OverconstrainedError") throw error
     console.warn("Exact camera mode is not available. Falling back to ideal resolution.", error)
     stream = await navigator.mediaDevices.getUserMedia(buildCameraConstraints(cameraId, false))
   }
   console.log("Got stream with constraints:", constraints)
   console.log("Camera settings:", stream.getVideoTracks()[0].getSettings())
   console.log("Camera capabilities:", stream.getVideoTracks()[0].getCapabilities?.())
   video.srcObject = stream
   await video.play()
 } catch (error) {
   if (error.name === "OverconstrainedError") {
     const v = constraints.video
     console.error(`The resolution ${v.width.exact}x${v.height.exact} px is not supported by your device.`)
   } else if (error.name === "NotAllowedError") {
     console.error("Permissions have not been granted to use your camera and " + "microphone, you need to allow the page access to your devices in " + "order for the demo to work.")
   }
   console.error(`getUserMedia error: ${error.name}`, error)
   return
 }
 const { videoWidth, videoHeight } = video
 const { usePortraitCanvas } = resizeCanvasesForCamera(videoWidth, videoHeight)
 const ctx = canvas.getContext("2d", { willReadFrequently: true })
 const animate = function () {
   drawCameraFrame(ctx, usePortraitCanvas)
   cameraAnimationFrame = requestAnimationFrame(animate)
 }
 animate()
}

		
Базовые понятия библиотеки распознавания

После настройки веб-сервера можно переходить к интеграции распознавания. Ниже приведены базовые понятия библиотеки распознавания Smart Engines.

  • Движок распознавания: создается один раз при старте приложения. От него создаются следующие элементы интерфейса:
  • Сессия распознавания: процесс распознавания конкретного документа (паспорта РФ, ВУ, СТС и других – всего библиотека поддерживает более 3000 типов). В сессию можно передать одно изображение или серию кадров из видеопотока: с каждым новым кадром результат уточняется; система автоматически определяет оптимальный момент остановки сессии распознавания.
  • Настройки сессии: хранят список доступных для распознавания типов документов (определяется конфигурационным бандлом), дополнительную информацию о каждом документе, а также позволяют настроить процесс распознавания: задать список ожидаемых типов документов для сессии распознавания, включить\выключить проверки признаков подлинности, задать таймаут для сессии распознавания и пр. Настройки создаются перед созданием самой сессии и не могут редактироваться в процессе распознавания.
  • Результат распознавания: объект со структурированными текстовыми полями, изображениями (например, фото владельца), координатами документа на кадре и данными проверок подлинности.

Общая логика работы одинакова для любой платформы и любого продукта линейки Smart Engines: движок → настройки сессии → создание сессии → передача изображения / последовательности изображений → разбор результата.

Инициализация движка распознавания

Необходимо создать и инициализировать один экземпляр движка распознавания. Инициализация является достаточно «тяжелой» операцией, поэтому ее следует выполнять вне потока пользовательского интерфейса. Для этого используется Web Worker.

При распознавании в браузере полезно учитывать следующую опцию инициализации:

  • bool lazy: ленивая и неленивая инициализация. При ленивой инициализации все необходимые ресурсы конфигурируются во время первого обращения к ним (как правило, при обработке первого кадра). Это позволяет эффективно использовать память при использовании библиотеки с большим набором поддерживаемых документов, но немного замедляет распознавание первого кадра. В неленивом режиме все ресурсы конфигурируются во время инициализации движка.

В случае наличия 1-2 документов в движке оптимальным будет использование не ленивой инициализации для ускорения обработки первого кадра, а в случае большого количества документов предпочтительнее использовать ленивый режим.

			// Init recognition engine
function createEngine(SE, MODULE) {
 try {
   console.time("Create Engine")
   ENGINE = new SE.seIdEngine(lazy, 1, false)
   console.timeEnd("Create Engine")

   wasm_module = SE

   readyEmitter({ version: SE.seIdEngineGetVersion() + " / " + MODULE + " / ", doclist: getAvailableDocList(ENGINE) })
 } catch (e) {
   errorEmitter({ message: e, SE })
 }
 return ENGINE
}

		
Конфигурирование сессии распознаванияМаска и режим

Сессия должна быть сконфигурирована для обнаружения конкретного типа документа или группы документов. Это осуществляется благодаря двум параметрам: маска (docTypes) и режим (mode):

Возможные значения маски и режима можно посмотреть в README конкретного WASM-модуля. Для примера с паспортом и свидетельством о рождении выберем режим anyrus (все российские документы) и любой тип (*).

			// Main config
const ENGINE_CONFIG = {
  activationUrl: "<activationUrl>",
  mode: "anyrus",
  docTypes: "*",
  signature: "<signature>",
}

spawnedSessionRGBA = createRGBASession(ENGINE, ENGINE_CONFIG)

		

Индивидуальная подпись (signature) привязана к конкретной сборке и необходима для инициализации сессии распознавания. Подпись представляет собой строку в 256 символов. activationUrl – адрес сервера активации, про который речь пойдет в следующем разделе.

Терминальность

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

  • Stoppers. Когда все поля уверенно распознались, движок выставляет результату флаг "терминальности". В этом случае подача дополнительных кадров не улучшит результаты.
  • Timeout. Когда истек заданный таймаут, чтобы ограничить возможное ожидание пользователя. Для режима Quality Gate характерное значение таймаута составляет около 15 секунд, чтобы пользователь успел скорректировать найденные проблемы.

Для такой настройки сессии необходимо поставить опции:

			sessionSettings.SetOption("common.enableStoppers", "true"
)
sessionSettings.SetOption("common.sessionTimeout", "15.0");

		
Quality Gate

Для включения Quality Gate необходимо указать следующие опции:

			sessionSettings.SetOption("common.enableImageQualityCheck", "true"
)
sessionSettings.SetOption("common.enableForensics", "true")
sessionSettings.SetOption("common.extractRectifiedDocumentImage", "true")

		

Последняя опция добавит вывод проективно исправленного и обрезанного изображения с лучшего кадра: как будто паспорт отсканировали и вырезали по границам.

Активация сессии распознавания

Поскольку WASM-модуль загружается с сервера на устройство пользователя, его достаточно легко скопировать. Для защиты от несанкционированного использования Smart ID Engine предусматривает привязку к инфраструктуре заказчика с помощью механизма активаций.

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

Рассмотрим пример. Создадим сессию:

			function createRGBASession(engine, ENGINE_CONFIG) {
 let sessionSettings = engine.CreateSessionSettings()
 sessionSettings.SetCurrentMode(ENGINE_CONFIG.mode)
 sessionSettings.AddEnabledDocumentTypes(ENGINE_CONFIG.docTypes)
 sessionSettings.SetOption("common.extractTemplateImages", "true")
 sessionSettings.SetOption("common.sessionTimeout", "15.0")
 sessionSettings.SetOption("common.enableImageQualityCheck", "true")
 sessionSettings.SetOption("common.enableForensics", "true")
 sessionSettings.SetOption("common.extractRectifiedDocumentImage", "true")

 spawnedSession = engine.SpawnSession(sessionSettings, ENGINE_CONFIG.signature)

 return spawnedSession
}

		

Активируем сессию:

			const checkSession = async (spawnedSession, ENGINE_CONFIG) => {
 if (spawnedSession.IsActivated()) {
   return
 }

 const dynKey = spawnedSession.GetActivationRequest() // Get dynamic key
 const response = await fetch(ENGINE_CONFIG.activationUrl, {
   method: "POST",
   headers: { "Content-Type": "application/json"},
   body: JSON.stringify({ action: "activate_id_session", message: dynKey }),
   signal: AbortSignal.timeout(3000),
 })

 if (!response.ok) {
   let desc = await response.json()
   throw new Error(desc.message)
 }

 const desc = await response.json()
 spawnedSession.Activate(desc.message) // Response is ok, activate session
}

		

Теперь сессию можно использовать для распознавания.

Изображение и процесс распознаванияСоздание объекта изображения документа Image

В браузере библиотека Smart Engines работает только с сырыми пикселями RGBA/RGB/YUV. Работа со сжатыми форматами jpg/gif/png/heic переложена на canvas браузера. Это позволяет читать изображения как с камеры, так и получить поддержку всех нативных форматов платформы и даже работу с PDF.

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

Процесс распознавания

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

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

Разбор результата распознавания с оценкой качества

В качестве результата система Smart Engines возвращает набор распознанных полей с текстом или изображениями (например, поле фотографии). При включении проверки качества в результат добавляется поле image_quality_score, которое содержит численную оценку качества изображения от 0 до 1, а также атрибут reason, который содержит список проблем изображения. Возможные проблемы:

  • finger_is_found (Найден палец)
  • zone_is_invalid (Зона обрезана)
  • hlt_is_found (Найден блик)
  • optic_is_found (Найдена голограмма)
  • «low DPI» (Низкое разрешение)
  • focus (Кадр не в фокусе)
  • luminosity (Слишком темно / слишком ярко)

При включении опции common.extractRectifiedDocumentImage добавляется поле-изображение rectified_document_image, содержащее проективно исправленное изображение.

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

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

Интеграция распознавания в веб-приложение

Рассмотрим интеграцию распознавания паспорта РФ в видеопотоке в веб-приложение. Чтобы распознавать изображения с камеры в реальном времени, понадобятся элементы video и canvas для вывода изображения с камеры.

			<video id="video" class="video" playsinline muted autoplay></video>
<canvas id="canvas" class="canvas"></canvas>

		

Кнопка для начала распознавания изображения:

			<button id="scan-button" class="button">
Сканировать документ
</button>

		

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

			<div id="result-wrapper" class="result-wrapper">
  <h2>Результат сканирования</h2>
  <div id="output"></div>
</div>

		
Клиентский JavaScript-кодРабота с камерой

В JavaScript-файле запрашиваем видеопоток с камеры:

			stream = await navigator.mediaDevices
    .getUserMedia({ video: { facingMode: { ideal: "environment" } } })

		

И направляем его в элемент video на странице:

			const videoEl = document.querySelector("#video")
videoEl.srcObject = stream
await videoEl.play()

		

Теперь на странице отображается видео с камеры.

Передаём видео в canvas.

Чтобы захватывать и передавать кадры на распознавание, необходимо предварительно отрисовать их на canvas.

			const canvasEl = document.querySelector("#canvas")
const ctx = canvasEl.getContext("2d", { willReadFrequently: true })
const animate = function () {
  ctx.drawImage(videoEl, 0, 0, canvasEl.width, canvasEl.height)
  requestAnimationFrame(animate)
}
animate()

		
Подключение движка

В процессе подключения движка определяются возможности среды исполнения (поддержка SIMD и многопоточности), и подключается соответствующий Wasm-модуль.

			// Import getModuleName()
let MODULE = await getModuleName()

// Import the relevant global Engine object
importScripts(`${ROOT_PATH}bin/${MODULE}/idengine_wasm.js`)

let module = {
  mainScriptUrlOrBlob: `${ROOT_PATH}bin/${MODULE}/idengine_wasm.js`,
  locateFile: (file) => `${ROOT_PATH}bin/${MODULE}/` + file,
}
// Init Wasm module
const SE = await SmartIDEngine(module)
// Init recognition engine
let ENGINE = createEngine(SE, MODULE)

const getModuleName = async () => {
  const a = async (e) => {
    try {
      return "undefined" != typeof MessageChannel && new MessageChannel().port1.postMessage(new SharedArrayBuffer(1)), WebAssembly.validate(e)
    } catch (e) {
      return !1
    }
  }

  const simd = async () => WebAssembly.validate(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11]))
  const threads = () => a(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 2, 1, 0, 5, 4, 1, 3, 1, 1, 10, 11, 1, 9, 0, 65, 0, 254, 16, 2, 0, 26, 11]))

  const hasSimd = await simd()
  const hasThreads = await threads()

  let module

  if (hasSimd === true) {
    hasThreads
      ? (module = "simd.threads")
      : (module = "simd.nothreads")
  } else {
    module = "nosimd.nothreads"
  }

  console.log("module")
  console.log(module)

  return module
}

		
Подключение веб-воркера

Подключим веб-воркер и передадим ему тип документа, который собираемся распознать:

			SEWorker = new Worker("./worker.js")
SEWorker.postMessage({
  requestType: "createSession",
  docData: "default:rus.passport.national"
})

		

По нажатию на кнопку, отправляем воркеру кадр из видео на распознавание:

			const scanButtonEl = document.querySelector("#scan-button")
scanButtonEl.addEventListener("click", async () => {
  SEWorker.postMessage({
    requestType: "frame",
    imageData: canvasEl.getContext("2d", { willReadFrequently: true })
      .getImageData(0, 0, canvasEl.width, canvasEl.height)
  })
})

		

Результат распознавания содержит текст и изображения отдельных элементов документа, выведем их:

			SEWorker.onmessage = function (message) {
  switch (message.data.requestType) {
    case "result":
      let result = message.data
      if (Object.keys(result.data).length === 0) {
        console.log("Документ не найден")
        SEWorker.postMessage({ requestType: "reset" })
        return
      }
      printResult(result)
      canvasHandler.clear(canvasOverlayEl)
      SEWorker.postMessage({ requestType: "reset" })
    break
}

		

Выведем результаты оценки качества:

			const textFields = getTextFields(streamResult)
const imageScore = textFields.image_quality_score

function printQualityToStatusBar (data) {
  const score = Number(data.imageScore.attr.last_frame_score) // числовая оценка качества кадра
  const indicator = getImageQualityIndicator(score) // цветовой индикатор качества кадра
  const message = getImageQualityProblems(data.imageScore) // сообщение о проблемах кадра

  print(`${indicator}\n${message}`)
}

function getImageQualityIndicator(value) {
  if (value >= 0.8) {
    return "🔘🔘🟢"
  } else if (value >= 0.4) {
    return "🔘🟡🔘"
  } else {
    return "🔴🔘🔘"
  }
}

function getImageQualityProblems(imageScore) {

  const verboseMessages = {
    finger_is_found: "Найден палец",
    zone_is_invalid: "Зона обрезана",
    hlt_is_found: "Найден блик",
    optic_is_found: "Найдена голограмма",
    "low DPI": "Низкое разрешение",
    focus: "Кадр не в фокусе",
    luminosity: "Слишком темно / слишком ярко",
  }

  let message = imageScore.attr && imageScore.attr.last_frame_verbose

  if (!message) return null

  message = message
    .split(";")
    .map((code) => code.trim())
    .filter(Boolean)
    .map((code) => {
      let translatedMessage = verboseMessages[code] || code
      return translatedMessage
    })
    .join("; ")

  return message
}

		

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

			function getCircles(result) {
 const arr = []
 for (let i = 0; i < result.GetTemplateSegmentationResultsCount(); i++) {
   // sr = IdTemplateSegmentationResult
   const sr = result.GetTemplateSegmentationResult(i)
   let ppvi = sr.ProblemPointsVectorBegin()
   for (; !ppvi.Equals(sr.ProblemPointsVectorEnd()); ppvi.Advance()) {
     const circle = ppvi.GetValue()
     arr.push([circle.x.x, circle.x.y, circle.r])
   }
 }
 return arr
}

case "frame": {
 let streamResult, frame

 try {
   await checkSession(spawnedSessionRGBA, ENGINE_CONFIG)
   frame = ImageFromRGBAbytes(msg.data.imageData, SE)
   streamResult = spawnedSessionRGBA.Process(frame)

   // Check terminality
   if (!streamResult.GetIsTerminal()) {
     const textFields = getTextFields(streamResult);
     const imageScore = textFields.image_quality_score
     postMessage({
       requestType: "FeedMeMore",
       imageScore: imageScore,
       templateDetection: getTemplateDetection(streamResult),
       templateSegmentation: getTemplateSegmentation(streamResult),
       templateCircles: getCircles(streamResult),
     })
     return
   }

if (result?.templateCircles) {
 for (let i = 0; i < result.templateCircles.length; i++) {
   const circle = result.templateCircles[i]
   if (!circle || circle.length < 3) continue
   const centerX = Number(circle[0])
   const centerY = Number(circle[1])
   const radius = Number(circle[2]) * 0.75
   if (!Number.isFinite(centerX) || !Number.isFinite(centerY) || !Number.isFinite(radius) || radius <= 0) {
     continue
   }
   ctx.beginPath()
   ctx.arc(centerX, centerY, radius, 0, Math.PI * 2)
   ctx.fillStyle = "rgba(255, 0, 0, 0.5)"
   ctx.fill()
   ctx.lineWidth = 3
   ctx.strokeStyle = "#ff0000"
   ctx.stroke()
 }
 if (this._circleTimer) {
   clearTimeout(this._circleTimer)
 }
 const noCirclesResult = {
   templateDetection: result?.templateDetection,
   templateSegmentation: result?.templateSegmentation,
 }
 this._circleTimer = setTimeout(() => {
   this.draw(noCirclesResult, overlayCanvas)
   this._circleTimer = null
 }, 250)
}

		

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

			const isTerminal = streamResult.GetIsTerminal();
const qualityIsGood = Number(imageScore?.value) >= 0.8;
const sessionTimeout = Number(ENGINE_CONFIG.sessionOptions.sessionTimeout);
const elapsedSeconds = (performance.now() - recognitionStartedAt) / 1000;
const timeoutReached = isTerminal && elapsedSeconds >= sessionTimeout;
needsMoreFrames = !(timeoutReached || (isTerminal && qualityIsGood))

		

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

Управление памятью

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

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

Пример освобождения памяти в finally, после получения данных о распознанном кадре:

			case "frame": {
  let streamResult, frame

  try {
    await checkSession(spawnedSessionRGBA, ENGINE_CONFIG)
    frame = ImageFromRGBAbytes(msg.data.imageData, SE)
    streamResult = spawnedSessionRGBA.Process(frame)

    if (!streamResult.GetIsTerminal()) {
      const textFields = getTextFields(streamResult);
      const imageScore = textFields.image_quality_score
      postMessage({
        requestType: "FeedMeMore",
        imageScore: imageScore,
        templateDetection: getTemplateDetection(streamResult),
        templateSegmentation: getTemplateSegmentation(streamResult),
        templateCircles: getCircles(streamResult),
      })
      return
    }

    const r = resultObject(streamResult, frame.GetSize())
    postMessage(r)
  } catch (e) {
    errorEmitter({ message: e, SE })
  } finally {
    if (streamResult?.GetIsTerminal()) spawnedSessionRGBA.Reset()
    streamResult?.delete()
    frame?.delete()
  }

  break

		
Заключение

Распознавание документов в браузере требует не только переноса OCR на веб-страницу, но и построения полноценного пользовательского сценария предъявления документа. Контроль качества изображения играет в нем определяющую роль, позволяя одновременно решать две задачи:

  • оценивать читаемость данных и при необходимости помогать пользователю;
  • защищаться от атак на предъявление в случае намеренного перекрытия реквизитов или значимых признаков подлинности документов.

Такой механизм позволяет минимизировать число неудачных попыток и сделать процесс идентификации прозрачным и максимально удобным для пользователя. А выполнение обработки непосредственно в браузере с помощью WebAssembly дает возможность реализовать этот сценарий непосредственно на пользовательском устройстве – без передачи изображений документов на внешний сервер.

Браузерное распознавание документов – не единственный вариант интеграции SDK Smart Engines. Та же библиотека может работать и на сервере, как часть десктоп-приложения (например, для работы с планшетными сканерами и веб-камерами) или мобильные приложения. Выбор конкретного варианта зависит от того, какие документы нужно распознавать и откуда поступает изображение – подробнее об этом можно прочитать на сайте Smart Engines.

Реклама. Рекламодатель: ООО «Смарт Энджинс Сервис» ИНН 7728328449, erid: 2W5zFK8Vyh3

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Как мы детектили фрод, а получили Джонни и Джулию: история одной браузерной разминки04.7517-08-2026
2AI-агенты сломали управление жизненным циклом идентификации. Что с этим делать0704-07-2026
3ИИ для автоматизации РКО. Как финансовые организации сокращают расходы и убирают ручной ввод документов5701-07-2026
4RAG-бот для любого сайта на Python за 60 строк кода014.526-08-2026
5Как написать отчёт по практике с помощью ИИ и готовых промптов?5702-07-2026
6«Сервис недоступен»: как войти на российские сайты 4 разными способами07.0105-08-2026
7Гибридное облако: что оставить у себя, а что вынести в публичный контур06.7726-08-2026
87 стратегий модернизации легаси: что рефакторить, переносить, заменять или переписывать04.425-08-2026
9F.A.Q. о профессии «Архитектор решений»011.7327-08-2026
10Облако или свои серверы: почему бизнес выбирает гибридную инфраструктуру08.3427-08-2026

Классификация: Мнения. Схожих патентов: 0. Схожих новостей: 10. Тональность: 0. Информативность: 5.5. Источник: tproger.ru.