WebAssembly — это технология, позволяющая запускать в браузере скомпилированный из разных языков код.
Наша библиотека написана на С++ и портирована в WebAssembly при помощи Emscripten.
Для максимального охвата устройств мы предоставляем 4 набора библиотек, каждая из которых должна загружаться в зависимости от поддержки конкретных фич браузера.
Описание сборок:
nosimd.nothreads— универсальная сборка, имеющая поддержку на большинстве устройств.simd.nothreads— самая быстрая сборка.simd.threads— сборка для постоянных задач по распознаванию. Поддержка многопоточности требует времени на инициализацию воркеров, которые по нашим подсчетам могут занимать больше времени, чем выполнение простой задачи с помощью сборкиsimd.nothreads.nosimd.threads— сборка, для редких случаев.
SIMD — это набор инструкций, который позволяет значительно увеличить скорость вычислений.
THREADS — поддержка многопоточности. Возможность распараллеливать вычисления возможна через создание дополнительных веб-воркеров, использующих общий SharedArrayBuffer. Для работы с ним необходимо дополнительно настраивать заголовки COOP и COEP на сервере.
Поддержку simd-инструкций и многопоточности в браузерах можно узнать с помощью библиотеки wasm-feature-detect.
Требования к серверу
Настройка веб-сервера является обязательной для оптимальной работы wasm.
Content-Type
Веб-сервер должен возвращать Content-Type: application/wasm для файлов с расширением *.wasm.
Compression
Веб-сервер должен поддерживать компрессию файлов c расширением .wasm. Wasm файлы хорошо сжимаются и это значительно позволит сократить время доставки файлов до клиента.
Убедитесь в наличии заголовка content-encoding у файла *.wasm при отдаче сервером в devtools, или используйте curl curl -H "Accept-Encoding: gzip" -I https://*.wasm
Рекомендуем отдавать wasm файлы уже сжатыми, чтобы не нагружать сервер сжатием файлов на лету на каждый запрос.
Интеграция Веб-приложение
WebAssembly-модуль
Для начала возьмём скомпилированный в WebAssembly модуль распознавания изображений от Smart Engines.
Он не требует настройки, его нужно просто положить в директорию проекта.
Web worker
Для того, чтобы не блокировать основной поток исполнения тяжёлыми задачами по распознаванию изображения, для общения с wasm-модулем мы будем использовать web worker, работающий в отдельном потоке.
Web worker является посредником в общении клиентского JavaScript-кода и wasm-модуля.
HTML-страница
Теперь создадим HTML-страницу.
Для того чтобы распознавать изображения в реальном времени, на странице нам понадобятся:
Элементы video и canvas для вывода изображения с камеры:
<video id="video" class="video" playsinline muted autoplay></video>
<canvas id="canvas" class="canvas"></canvas>
Кнопки для начала распознавания изображения и сверки лиц:
<button id="scan-button" class="button">
Сканировать документ
</button>
<button id="capture-face-button" class="button">
Сверить лицо
</button>
Блок для вывода результатов распознавания:
<div id="result-wrapper" class="result-wrapper">
<h3>Результат сканирования</h3>
<div id="output"></div>
</div>
Теперь подключим к странице JavaScript-файл.
Клиентский JavaScript-код
Работа с камерой
В JavaScript-файле запросим видеопоток с камеры:
stream = await navigator.mediaDevices
.getUserMedia({ video: { facingMode: { ideal: "environment" } } })
И направим его в наш видео-элемент на странице:
const videoEl = document.querySelector("#video")
videoEl.srcObject = stream
await videoEl.play()
Теперь на странице отображается видео с камеры.
Передадим видео в 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()
Подключение воркера
Затем подключим воркер и передадим ему тип документа, который мы собираемся распознать:
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("Document not found")
SEWorker.postMessage({ requestType: "reset" })
return
}
printResult(result)
canvasHandler.clear(canvasOverlayEl)
SEWorker.postMessage({ requestType: "reset" })
break
}
Минимальное веб-приложение с возможностью распознавания изображений готово.