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

Одним из самых востребованных направлений в OCR сейчас является распознавание кодифицированных объектов, таких как баркоды, номера телефонов, банковских карт и прочие машиночитаемые зоны. В своё время мы выделили распознавание таких объектов в отдельный продукт Smart Code Engine для того, чтобы иметь возможность гибче работать с различными сценариями распознавания, а так же иметь возможность пойти дальше в деле оптимизации скорости и размера библиотеки. В результате появился Smart Code Engine 2.0 - продукт получил новый интерфейс и возможность максимально гибко настраивать поведение для получения лучшего качества распознавания. О том, как с его помощью развернуть распознавание баркодов в браузере, мы сейчас и расскажем.

Интерфейс библиотеки Smart Code Engine довольно прост, написан на С++, и с его помощью распознавание баркодов реализуется вот так:

int main(int argc, char argv) {
  try {
  // Инициализируем набор инструментов для распознавания
    std::unique_ptr<se::code::CodeEngine> engine(
        se::code::CodeEngine::CreateFromEmbeddedBundle(true));

  // Создаём объект с настройками для распознавания: 
  //  с его помощью мы можем настраивать поведение библиотеки при распознавании (например, тип распознаваемого баркода)
    std::unique_ptr<se::code::CodeEngineSessionSettings> settings(
        engine->GetDefaultSessionSettings());

  // Задаём тип объекта, который будет искаться библиотекой на картинке
    if (engine->IsEngineAvailable(se::code::CodeEngine_Barcode)) {
      std::string engine_name = toString(EngineSettingsGroup::Barcode);
      settings->SetOption((engine_name + ".enabled").c_str(), "true");

      // Включаем распознавние всех поддерживаемых типов баркодов
      settings->SetOption((engine_name + ".COMMON.enabled").c_str(), "true");
   }

  // Создаём сессию распознавания - объект, который выбирает и использует нужные инструменты распознавания, а также хранит все промежуточные данные
    std::unique_ptr<se::code::CodeEngineSession> session(engine->SpawnSession(
        *settings, “personal_signature”));

  // Создаём объект, хранящий изображение в понятном для нашей системы виде
    std::unique_ptr<se::common::Image> image(
      se::common::Image::FromFile(“path_to_image”));
    
  // Запускаем распознавание на картинке
    const se::code::CodeEngineResult& result = session->Process(*snapshot);
    
  // Достаём результат распознавания из объекта, хранящего результат
 for (auto it_field = code_object.FieldsBegin();
         it_field != code_object.FieldsEnd(); ++it_field) {
      const se::code::CodeField &code_field = it_field.GetValue();
      if (code_field.HasBinaryRepresentation()) {
        ::printf("        Base64 BinaryRepresentation: %s\n",
            code_field.GetBinaryRepresentation().GetBase64String().GetCStr());
      }
      if (code_field.HasOcrStringRepresentation())
        ::printf("        Ocr string representation: %s\n",
                 code_field.GetOcrString().GetFirstString().GetCStr());
    }
  } // ловим и обрабатываем исключения
 catch (const se::common::BaseException& e) {
    printf("Exception thrown: %s\n", e.what());
    return -1;
  }
  return 0;
}

В плюсах нам нужно скомпилировать этот код, прилинковав к поставляемой нами библиотеке, и запустить - вуаля, баркод распознан!

Qr-код, который будем распознавать
Qr-код, который будем распознавать

Qr-код, который будем распознавать

$ ./codeengine_sample 

    Base64 BinaryRepresentation: U21hcnQgRW5naW5lcw==
    Ocr string representation: Smart Engines 

Теперь давайте разберемся, как это будет выглядеть в браузере. Нам нужно что-то вроде библиотеки, которую можно загрузить в браузер, как-то получить её интерфейс и воспользоваться им по аналогии с плюсовым примером. WebAssembly предлагает такой механизм - наш код собирается в "модуль", который загружается на страницу, дальше "компилируется" в js-объект со своим списком методов и полей. Лежит этот объект в специально  выделенном массиве uint8 с правом исполнения кода, находящегося в нём. Самый стандартный способ добиться этого - воспользоваться Emscripten SDK. Конечно, существует целый набор способов, позволяющих скомпилировать код на C++ в WASM, однако именно Emscripten предоставляет максимально простой и хорошо задокументированный инструментарий для этой задачи. (А за возможность штатно работать с этим с помощью cmake отдельное ребятам спасибо). Особо тут рассказывать нечего, для оборачивания интерфейса мы пользуемся входящим в состав emscripten инструментом embind, а в результате компиляции получаем на выходе два (три, если речь ещё и о многопоточности) файла - codeengine_wasm.js и codeengine_wasm.wasm. Первый является js-сахаром для удобства подключения модуля и работы с ним, второй и есть wasm-модуль.

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

 <body>
    <div class="wrapper">
      <header class="header">
        <h1>CodeEngine wasm sample</h1>
      </header>
      <div class="buttons" id="buttons">
        <input type="file" id="select_file" accept="image/png, image/jpeg" />
      </div>
      <div class="wasm"></div>
      <div class="video-wrapper">
        <div class="element-wrapper">
          <div class="element" id="output"></div>
        </div>
      </div>
    </div>
//Вызываем вспомогательный скрипт, который сгенерил нам Emscripten
  <script src="./codeengine_wasm.js"></script>
//Сам скрипт встраивания
  <script type="module">
    const canvas = document.querySelector('#canvas');
    const overlayCanvas = document.querySelector('#overlayCanvas');
    const fileSelector = document.querySelector('#select_file');
    const buttons = document.querySelector('#buttons');
    buttons.style.display = 'block';
    const output = document.querySelector('#output');
    const log = document.querySelector('.log');
    const wasm = document.querySelector('.wasm');
    // Компилируем модуль и создаём его инстанс
    const SE = await SmartCodeEngine();
    onsole.log("SmartCodeEngine module downloaded and compiled");
    // Выберем картинку и загрузим её в память - напрямую мы не можем
    // общаться с файловой системой, поэтому делаем это штатными средствами 
    fileSelector.addEventListener('change', (event) => {
      const file = event.target.files[0];
      if (file.type && file.type.indexOf('image') === -1) {
        console.log('File is not an image.', file.type, file);
        return;
      }
      const img = new Image();
      img.onload = function () { };
      img.src = URL.createObjectURL(file);
      const reader = new FileReader();
      reader.addEventListener('load', (event) => {
        // Само распознавание сосредоточено здесь
      try {
        let engine = new SE.CodeEngine();
        console.log("SE.CodeEngine created");
        let sessionSettings = engine.GetDefaultSessionSettings();
        let engine_name = SE.ToString(SE.EngineSettingsGroup.Barcode);
        sessionSettings.SetOption(engine_name + ".enabled", "true");
        sessionSettings.SetOption(engine_name + ".COMMON.enabled", "true");
        sessionSettings.SetOption(engine_name + ".maxAllowedCodes", "1");
        sessionSettings.SetOption(engine_name + ".roiDetectionMode", "anywhere");
        sessionSettings.SetOption(engine_name + ".feedMode", "sequence");
        //Создаём объект с картинкой
        const imgSrc = new SE.seImage(event.target.result);
        let spawnedSession = engine.SpawnSession(sessionSettings, signature);
        const result = spawnedSession.Process(imgSrc);
        console.log("Recognition result received");
    //Выводим результат распознавания
        let data = [];
        const co = result.ObjectsBegin();
        for (; !co.Equals(result.ObjectsEnd()); co.Advance()) {
          const code_object = co.GetValue();
          let object_data ={'type':code_object.GetTypeStr(), 'attributes':{}}
          const tf = code_object.FieldsBegin();
          for (; !tf.Equals(code_object.FieldsEnd()); tf.Advance()) {
            const code_field = tf.GetValue();
            let code_field_name = code_field.Name();
            let value_binary="";
            let value_string="";
            if (code_field.HasBinaryRepresentation()) {
              value_binary = code_field.GetBinaryRepresentation().GetBase64String();
              console.log("value_binary is " + value_binary);
            }
            if (code_field.HasOcrStringRepresentation())
            value_string = code_field.GetOcrString().GetFirstString();
            console.log("value_string is " + value_string);
            object_data[code_field_name] = {
              name: code_field_name,
              value_binary: value_binary,
              value_string: value_string,
              isAccepted: code_field.IsAccepted()
            };
          }
          data.push(object_data);
        }        let output_data = '';
        for (const obj in data) {
          output_data += `<div class="output-data">
          <strong>${data[obj]["type"]}</strong>
          </div>`;
            for (const prop in data[obj]) {
              if (prop != "type") {
                output_data += `<div class="output-data">
                <b>${prop}</b>
                <span>${data[obj][prop].value_binary}</span>
                <span>${data[obj][prop].value_string}</span>
                </div>`;
              }
            }
            output_data += '<hr>';
        }
        output.innerHTML = output_data;
        
        imgSrc.delete();
        result.delete();
        spawnedSession.delete();
        sessionSettings.delete();
        engine.delete();
      } catch (e) {
        console.error(SE.printExceptionMessage(e));
      }
    });
    reader.readAsArrayBuffer(file);
  });
    </script>
  </body>

Результат встраивания

Как видите, работа с Smart Code Engine SDK довольно проста, встраивание обеспечивается моментально, а скорость работы распознавания на уровне нативных библиотек! Размер получившегося модуля для распознавания всего 1,9 мегабайта (при использовании сжатия gzip, поддерживаемого всеми современными браузерами).

P.S. Нашим клиентам, разумеется, мы предоставляем SDK для встраивания в браузер с уже готовыми модулями, документацией, примерами встраивания и поддержкой программистов делом и добрым словом :)

Комментарии (0)