Как интегрировать распознавание документов в браузере через WebAssembly с оценкой качества изображения. Пошаговый гайд по SDK Smart Engines для KYC.— Читать дальше «Распознавание документов в браузере с оценкой качества изображения: пошаговая интеграция»
Браузеры стали ключевым каналом для удалённой идентификации: цифрового онбординга, KYC и похожих сценариев, где требуется подтвердить личность по документу. В таких сценариях пользователь сам распознает документ с помощью мобильного устройства. Отдельная задача внутри этого процесса – оценка качества изображения: если палец, блик или посторонний предмет перекрыли значимую часть документа, система сразу подсказывает пользователю, что именно мешает пройти проверку KYC. Кроме того, оценка качества изображения позволяет защититься от атак на предъявление (PAD) – когда реквизиты или признаки подлинности документа оказываются перекрыты намеренно.
Ниже рассмотрим два кейса: распознавание паспорта в видеопотоке и распознавание свидетельства о рождении по фотографии.
Оценка качества изображенияОграничения на дистрибуцию нативных приложений в сторах подталкивают бизнес переносить основные клиентские процессы, включая идентификацию по документам, в браузер. В этом сценарии пользователь сам подносит документ к камере смартфона, планшета или ноутбука, без установки отдельного приложения.
Самостоятельное распознавание на стороне клиента имеет свои особенности: палец, посторонний предмет, блик или голограмма могут перекрыть значимые реквизиты или признаки подлинности документа, а часть кадра может оказаться не в фокусе или за границей снимка. Неважно, произошло это случайно или намеренно: в обоих случаях системе важно вовремя распознать проблему, определить её тип и в реальном времени проинструктировать пользователя, что именно мешает пройти KYC.
Законодательство РФ (включая 115-ФЗ) устанавливает конкретные требования к идентификации личности. Поэтому в случае с распознаванием недостаточно извлечь отдельные поля документа – нужно убедиться, что в кадре присутствуют все данные и элементы, необходимые для проверки. Контроль качества изображения в процессе распознавания документов решает эту задачу вместе с ещё одной: защищает от мошеннических атак.
При этом документы, используемые для подтверждения личности, отличаются форматом, шаблоном и набором защитных элементов, поэтому оценка качества должна учитывать особенности конкретного типа. К этому добавляется разница во входных данных: происходит ли распознавание в видеопотоке или по одному изображению. Дальше разберём два сценария, где эти различия проявляются особенно наглядно – распознавание паспорта в видеопотоке и распознавание свидетельства о рождении по фотографии.
Распознавание в браузере с помощью WebAssemblySmart ID Engine может работать в браузере в виде WebAssembly-модуля. Это позволяет выполнять распознавание непосредственно на стороне клиента, не отправляя изображение документа на сервер.
Браузеры предоставляют следующие фичи в зависимости от возможностей устройства:
Проверить поддержку SIMD-инструкций и многопоточности в браузере можно с помощью библиотеки wasm-feature-detect.
Smart Engines предоставляет набор библиотек для всех комбинаций этих фич:
До подключения библиотеки к веб-приложению необходимо подготовить сервер, с которого браузер будет получать WASM-модули.
Первые два пункта являются строго обязательными для нормального функционирования библиотеки в браузере.
Например, в 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.
Общая логика работы одинакова для любой платформы и любого продукта линейки Smart Engines: движок → настройки сессии → создание сессии → передача изображения / последовательности изображений → разбор результата.
Инициализация движка распознаванияНеобходимо создать и инициализировать один экземпляр движка распознавания. Инициализация является достаточно «тяжелой» операцией, поэтому ее следует выполнять вне потока пользовательского интерфейса. Для этого используется Web Worker.
При распознавании в браузере полезно учитывать следующую опцию инициализации:
В случае наличия 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 – адрес сервера активации, про который речь пойдет в следующем разделе.
ТерминальностьВ случае видеопотока необходимо определить момент, когда закончить передавать кадры в сессию и решить, что результат распознавания является финальным. Сессия может завершиться в следующих случаях:
Для такой настройки сессии необходимо поставить опции:
sessionSettings.SetOption("common.enableStoppers", "true"
)
sessionSettings.SetOption("common.sessionTimeout", "15.0");
Для включения 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, который содержит список проблем изображения. Возможные проблемы:
При включении опции 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-файле запрашиваем видеопоток с камеры:
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 | Как мы детектили фрод, а получили Джонни и Джулию: история одной браузерной разминки | 0 | 4.75 | 17-08-2026 |
| 2 | AI-агенты сломали управление жизненным циклом идентификации. Что с этим делать | 0 | 7 | 04-07-2026 |
| 3 | ИИ для автоматизации РКО. Как финансовые организации сокращают расходы и убирают ручной ввод документов | 5 | 7 | 01-07-2026 |
| 4 | RAG-бот для любого сайта на Python за 60 строк кода | 0 | 14.5 | 26-08-2026 |
| 5 | Как написать отчёт по практике с помощью ИИ и готовых промптов? | 5 | 7 | 02-07-2026 |
| 6 | «Сервис недоступен»: как войти на российские сайты 4 разными способами | 0 | 7.01 | 05-08-2026 |
| 7 | Гибридное облако: что оставить у себя, а что вынести в публичный контур | 0 | 6.77 | 26-08-2026 |
| 8 | 7 стратегий модернизации легаси: что рефакторить, переносить, заменять или переписывать | 0 | 4.4 | 25-08-2026 |
| 9 | F.A.Q. о профессии «Архитектор решений» | 0 | 11.73 | 27-08-2026 |
| 10 | Облако или свои серверы: почему бизнес выбирает гибридную инфраструктуру | 0 | 8.34 | 27-08-2026 |