Минимальный пример интеграции в Android-приложение на Kotlin.
Состав SDK:
doc
— документацияSample/
— семплSample/app/src/main/jniLibs/
— универсальная статическая библиотека для android устройств и эмулятора. Sample/app/src/main/libs/*.jar
— обертка для С++ библиотеки Sample/app/src/main/assets/data/*.se
— конфигурационный файлВсе файлы, предоставляемые в виде исходных кодов могут быть изменены на ваше усмотрение.
C++ библиотека поставляется в виде бинарных файлов для разных архитектур. Java взаимодействует с ней через JNI(Java Native Interface); обёртка .jar
для C++ кода создана с помощью SWIG.
Любой объект, возвращаемый функцией JNI (то есть методами нашей обёртки), — это локальная ссылка, действительная только в рамках вызова метода в текущем потоке. Сам движок при этом продолжает существовать глобально.
При неправильном использовании таких ссылок велика вероятность получить NullPointerException
в разные моменты работы приложения. Поэтому старайтесь сразу сохранять результат вызова в нативные Java-объекты, чтобы не хранить ссылки на структуры нашей библиотеки.
Подробнее о правилах работы с JNI: Android docs.
jniLibs
и поместите ее в ваш проект по пути app/src/main/jniLibs/
jni*.jar
и поместите его в ваш проект по пути app/src/main/libs/
. *.se
и поместите его в ваш проект по пути app/src/main/assets/data/*.se
.dependencies {
implementation(fileTree("libs") { include("*.jar") })
}
-keep class com.smartengines.common.* { public <methods>; }
-keep class com.smartengines.id.* { public <methods>; }
Подключите этот файл в build.gradle (module).
apply plugin: 'com.android.application'
android {
compileSdk 36
...
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' /
}
debug {
minifyEnabled false
}
}
}
...
Взаимодействие с библиотекой происходит по следующему сценарию:
Если библиотека используется во всём приложении — инициализируйте её один раз при старте процесса в Application.onCreate()
,
Если библиотека нужна только в отдельных частях приложения — инициализируйте её в Activity.onCreate()
.
Для начала загружаем нативную библиотеку
System.loadLibrary("jniidengine")
Затем читаем данные из файла конфигурации
// Open an input stream
val stream: InputStream = context.assets.open("your_bundle_filename", AssetManager.ACCESS_STREAMING)
// Read data from the stream
val size = stream.available()
val data = ByteArray(size)
val result = stream.read(data)
stream.close()
if (result != size) throw Exception("stream reading error")
После этого вызываем функцию загрузки движка (и передаем ей загруженные ранее данные конфигурации)
// engine init
val engine = IdEngine.Create(data,
true, // lazyConfiguration
0, // initConcurrency
true // delayedInitialization
)
Сессия распознавания может быть сконфигурирована для распознавания конкретных типов документов, настройки таймаута, необходимости получить в результате распознавания проективно исправленные изображения и так далее.
// Create session settings
val sessionSettings = engine.CreateSessionSettings()
// Fill the session setting according to the target
with(sessionSettings){
// Common
SetOption("common.sessionTimeout", "5.0")
// ID session settings
SetCurrentMode(mode)
AddEnabledDocumentTypes(mask)
}
// Create Session
val session = engine.SpawnSession(
sessionSettings,
signature
)
Независимо от источника изображения (серия изображений с камеры или выбор изображения из галереи) необходимо создать изображение класса se.common.Image
.
Передаем сессии одну картинку и сразу получаем результат
fun processPhoto(image: Image) {
// PROCESS THE IMAGE (long process)
val result = session.Process(image)
}
При работе с видео потоком обрабатывайте кадры последовательно в рамках одной и той же сессии.
Система может распознать документ с высокой точностью и по одному изображению, однако, использование результатов распознавания нескольких изображений приводит к существенному повышению качества.
Система комбинирует результаты распознавания, поступившие из разных кадров. Это позволяет уверенно распознавать документы в условиях плохого освещения, наличия бликов и других неблагоприятных факторов. Механизм, который принимает решение о необходимости распознавания дополнительных кадров или остановке процесса называется терминальностью.
Терминальность — автоматическая остановка процесса распознавания в видеопотоке. Принимает значение true в двух случаях:
Таким образом вы всегда получаете ответ от системы.
fun processVideoFrame(image: Image) {
// PROCESS THE IMAGE (long process)
val result = session.Process(image)
// Check the result is terminal
if ( result.GetIsTerminal() ) ... stop the process here
}
Полученный в результате распознавания результат (объект класса IdResult) содержит все данные документа. Для их получения используются различные методы класса, описанные в документации.
В качестве критерия для проверки наличия результата после завершения (терминальности) сессии рекомендуется использовать метод result.GetDocumentType(). Возвращаемое значение доступно на протяжении всей сессии, при условии, что распознавание выполнялось хотя бы один раз.
Для анализа промежуточных результатов распознавания рекомендуется использовать метод result.GetTemplateDetectionResulsCount(), поскольку он позволяет определить наличие документа в текущем кадре.
Получение текстовых полей документа:
val iterator = result.TextFieldsBegin()
val end = result.TextFieldsEnd()
while (!iterator.Equals(end)) {
// Load the field data
val textFiaeld : IdTextField = iterator.GetValue()
with(textField){
val info = GetBaseFieldInfo()
val key = GetName(), //the same as iterator.GetKey(),
val value = GetValue().GetFirstString().GetCStr(),
val isAccepted = info.GetIsAccepted(),
val attr = info.parseAttributes()
}
// Next field
iterator.Advance()
}
Рантайм Java не управляет памятью в C++. Garbage collector не имеет доступа к объектам, которые порождает С++. Большинство классов нашей библиотеки содержат фабричные методы, возвращающие указатели на объекты, выделенные в куче (new
, new[]
, malloc
, factory
возвращает указатель), поэтому ответственность за освобождение памяти лежит на том, кто пользуется этим объектом!
Обязательно освобождайте ресурсы, как перестаете в них нуждаться.
session.Reset()
, image.delete()
, engine.delete()
Наша библиотека может выдавать исключения подклассов se::common::BaseException
при неверном вводе данных, некорректных вызовах и других ошибках. Не забывайте их обрабатывать.
Заказать продукт
Для заказа решений, получения подробной информации или триал версий
заполните приведенную ниже форму, и мы обязательно с Вами свяжемся.