№ п/п
|
№ тест-кейса
|
Дополнительные условия
|
Результат
|
1
|
1
|
|
пройден
|
2
|
2
|
|
пройден
|
3
|
3
|
|
пройден
|
4
|
4
|
|
пройден
|
5
|
5
|
|
пройден
|
6
|
6
|
Естественное боковое освещение, Oneplus One
|
точность - 92,97 %
|
7
|
6
|
Естественное боковое освещение, Xiaomi Redmi 4X
|
точность - 91,61 %
|
8
|
6
|
Естественное боковое освещение, Xiaomi Mi4c
|
точность - 90,95 %
|
9
|
6
|
Искусственное локализованное освещение, Oneplus One
|
точность - 97,83 %
|
10
|
6
|
Искусственное локализованное освещение, Xiaomi Redmi 4X
|
точность - 94,47 %
|
11
|
6
|
Искусственное локализованное освещение, Xiaomi Mi4c
|
точность - 93,22 %
|
12
|
7
|
Естественное боковое освещение, Oneplus One
|
точность - 92,31 %
|
13
|
7
|
Естественное боковое освещение, Xiaomi Redmi 4X
|
точность - 90,66
%
|
14
|
7
|
Естественное боковое освещение, Xiaomi Mi4c
|
точность - 90,44
%
|
15
|
7
|
Искусственное локализованное освещение, Oneplus One
|
точность - 97,98
%
|
16
|
7
|
Искусственное локализованное освещение, Xiaomi Redmi 4X
|
точность - 97,23 %
|
17
|
7
|
Искусственное локализованное освещение, Xiaomi Mi4c
|
точность - 94,21 %
|
18
|
8
|
|
пройден
|
19
|
9
|
|
пройден
|
20
|
10
|
|
пройден
|
Тестирование производительности показало, что среднее время распознавания
данных паспорта составляет 832 мс, а среднее время распознавания данных
страхового свидетельства - 581 мс, что удовлетворяет требованию к максимальному
времени распознавания в 1 с.
Среднее время внесения данных при помощи оптического распознавания
составило для паспорта 39,8 с, для страхового свидетельства - 23,4 с. Замер
времени производился между запуском операции распознавания (RecognitionActivity) и нажатием на кнопку сохранения
документа в БД.
Для оценки скорости внесения данных документов вручную была использована
операция DocumentActivity с пустыми текстовыми полями. Время
замерялось между началом ввода и нажатием кнопки «Сохранить» и в среднем
составило: для паспорта - 1 мин 23,9 с, для страхового свидетельства - 1 мин 1
с.
Таким образом, для паспорта время автоматического ввода оказалось меньше,
чем ручного, в раза, а для страхового свидетельства - в раза.
ЗАКЛЮЧЕНИЕ
В ходе выполнение выпускной квалификационной работы был проведен анализ
существующих решений по распознаванию данных документов, удостоверяющих
личность, изучены теоретические основы в области компьютерного зрения и
алгоритмы обработки изображений и оптического распознавания символов.
Была спроектирована база данных для использования в приложении.
Разработаны алгоритмы обнаружения и распознавания текста в документе,
использующие преимущественно возможности библиотеки OpenCV. Спроектирован пользовательский интерфейс приложения
на ОС Android.
Было разработано приложение в интегрированной среде разработки Android Studio. К приложению подключена локальная БД SQLite для сохранения данных распознанных
документов. Реализован простой механизм аутентификации пользователя в системе.
Также была обучена библиотека Tesseract для шрифтов, используемых в паспортах РФ и страховых свидетельствах.
В результате тестирования приложения ошибок в его работе не обнаружено.
Точность распознавания зависит от условий освещенности и камеры устройства и в
среднем составляет не менее 90%. Также стоит отметить, что интересующий текст
на документах может быть плохо пропечатан или, например, сливаться с другими
объектами, что тоже оказывает отрицательное влияние на точность распознавания.
Время, затрачиваемое на распознавание, удовлетворяет техническому заданию.
Использование технологий оптического распознавания дало прирост к скорости
внесения данных документов более чем в 2 раза по сравнению с ручным вводом.
Приложение может использоваться на смартфонах с ОС Android 4.0 и выше, имеющих камеру с
разрешением не менее 2 Мп с автофокусом, и не требует доступ к интернету.
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
1. Технологии
перевода бумажных документов в электронные [Электронный ресурс] - Режим
доступа: http://compress.ru/article.aspx?id=11802
2.
Comparison of optical character recognition software [Электронный ресурс] -
https://en.wikipedia.org/wiki/Comparison_of_optical_character_recognition_software
.
Improving the quality of the output [Электронный ресурс] -
https://github.com/tesseract-ocr/tesseract/wiki/ImproveQuality
4. Компьютерное зрение
[Электронный ресурс] - Режим доступа: ru.wikipedia.org/wiki/Компьютерное_зрение
<https://ru.wikipedia.org/wiki/Компьютерное_зрение>
. Выделение границ
[Электронный ресурс] - Режим доступа: ru.wikipedia.org/wiki/Выделение
<https://ru.wikipedia.org/wiki/Компьютерное_зрение>_границ
. Оператор Кэнни [Электронный
ресурс] - Режим доступа: ru.wikipedia.org/wiki/
<https://ru.wikipedia.org/wiki/Компьютерное_зрение>Оператор_Кэнни
. Математическая морфология
[Электронный ресурс] - Режим доступа: ru.wikipedia.org/wiki/
<https://ru.wikipedia.org/wiki/Компьютерное_зрение>Математическая_морфология
. Операции [Электронный
ресурс] - Режим доступа: https://developer.android.com/guide/components/activities.html?hl=ru
. Макеты [Электронный ресурс]
- Режим доступа:
https://developer.android.com/guide/topics/ui/declaring-layout.html?hl=ru
. jTessBoxEditor
[Электронный ресурс] - Режим доступа: http://vietocr.sourceforge.net/training.html
. Dashboards
[Электронный ресурс] - Режим доступа:
https://developer.android.com/about/dashboards/index.html
. Bytedeco
javacv-android-camera-preview [Электронный ресурс] - Режим доступа:
https://github.com/bytedeco/sample-projects/tree/master/javacv-android-camera-preview
. Mat - The Basic Image Container [Электронный ресурс] - Режим доступа:
http://docs.opencv.org/3.2.0/d6/d6d/tutorial_mat_the_basic_image_container.html
. Паспорт гражданина
Российской Федерации [Электронный ресурс] - Режим доступа:
https://ru.wikipedia.org/wiki/Паспорт_гражданина_Российской_Федерации
. Geometric Image
Transformations [Электронный ресурс] - Режим доступа: http://docs.opencv.org/3.2.0/da/d54/group__imgproc__transform.html#gga5bb5a1fea74ea38e1a5445ca803ff121acf959dca2480cc694ca016b81b442ceb
. Image Thresholding
[Электронный ресурс] - Режим доступа:
http://docs.opencv.org/trunk/d7/d4d/tutorial_py_thresholding.html
. Saving Data in SQL
Databases [Электронный ресурс] - Режим доступа:
https://developer.android.com/training/basics/data-storage/databases.html
18. Defining
Global Variables in Android [Электронный ресурс] - Режим доступа:
https://androidresearch.wordpress.com/2012/03/22/defining-global-variables-in-android/
19. Training
Tesseract [Электронный ресурс] - Режим доступа: https://github.com/tesseract-ocr/tesseract/wiki/Training-Tesseract
. String
similarity test [Электронный ресурс] - Режим
доступа: https://www.tools4noobs.com/online_tools/string_similarity
ПРИЛОЖЕНИЕ 1
(обязательное)
Листинг файла манифеста AndroidManifest.xml
<?xml
version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"="demidov.docrecognition">
<uses-permission
android:name="android.permission.CAMERA" />
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
<uses-feature
android:name="android.hardware.camera.autofocus" />
<application:name=".DocRecogApplication":allowBackup="true":icon="@mipmap/ic_launcher":label="@string/app_name":roundIcon="@mipmap/ic_launcher_round":supportsRtl="true":theme="@style/AppTheme">
<activity:name=".RecognitionActivity":screenOrientation="portrait"></activity>
<activity:name=".LoginActivity":label="@string/title_activity_login">
<intent-filter>
<action
android:name="android.intent.action.MAIN" />
<category
android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity:name=".OperationActivity":label="@string/title_activity_operation"
/>
<activity:name=".DocumentActivity":label="@string/title_activity_document"
/>
<activity:name=".DocListActivity":label="@string/title_activity_doclist"
/>
</application>
</manifest>
ПРИЛОЖЕНИЕ 2
(обязательное)
Листинг
макета activity_login.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android":tools="http://schemas.android.com/tools":layout_width="match_parent":layout_height="match_parent":gravity="center_horizontal":orientation="vertical":paddingBottom="@dimen/activity_vertical_margin":paddingLeft="@dimen/activity_horizontal_margin":paddingRight="@dimen/activity_horizontal_margin":paddingTop="@dimen/activity_vertical_margin":context="demidov.docrecognition.LoginActivity">
<!-- Login progress -->
<ProgressBar:id="@+id/login_progress"="?android:attr/progressBarStyleLarge":layout_width="wrap_content":layout_height="wrap_content":layout_marginBottom="8dp":visibility="gone"
/>
<ScrollView:id="@+id/login_form":layout_width="match_parent":layout_height="match_parent">
<LinearLayout:id="@+id/username_login_form":layout_width="match_parent":layout_height="wrap_content":orientation="vertical">
<android.support.design.widget.TextInputLayout:layout_width="match_parent":layout_height="wrap_content">
<AutoCompleteTextView:id="@+id/username":layout_width="match_parent":layout_height="wrap_content":hint="@string/prompt_username":maxLines="1":singleLine="true"
/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout:layout_width="match_parent":layout_height="wrap_content">
<EditText:id="@+id/password":layout_width="match_parent":layout_height="wrap_content":hint="@string/prompt_password":imeActionId="@+id/login":imeActionLabel="@string/action_sign_in_short":imeOptions="actionUnspecified":inputType="textPassword":maxLines="1":singleLine="true"
/>
</android.support.design.widget.TextInputLayout>
<Button:id="@+id/sign_in_button"="?android:textAppearanceSmall":layout_width="match_parent":layout_height="wrap_content":layout_marginTop="16dp":text="@string/action_sign_in":textStyle="bold"
/>
</LinearLayout>
</ScrollView>
</LinearLayout>
ПРИЛОЖЕНИЕ 3
(обязательное)
Листинг
макета activity_operation.xml
<?xml
version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android":app="http://schemas.android.com/apk/res-auto":tools="http://schemas.android.com/tools":layout_width="match_parent":layout_height="match_parent":context="demidov.docrecognition.OperationActivity">
<RadioGroup:layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toTopOf="parent":layout_marginTop="8dp":id="@+id/operations">
<RadioButton:id="@+id/createDoc":layout_width="wrap_content":layout_height="wrap_content":text="@string/create_document":onClick="selectOperation"
/>
<RadioButton:id="@+id/existingDoc":layout_width="wrap_content":layout_height="wrap_content":text="@string/view_existing_document":onClick="selectOperation"
/>
</RadioGroup>
<RadioGroup:layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="0dp":layout_marginTop="8dp":visibility="gone":layout_constraintTop_toBottomOf="@+id/operations":layout_constraintLeft_toLeftOf="@+id/operations":id="@+id/docTypes">
<RadioButton:id="@+id/passport":layout_width="wrap_content":layout_height="wrap_content":onClick="selectDocType":text="@string/passport"
/>
<RadioButton:id="@+id/socialSecurity":layout_width="wrap_content":layout_height="wrap_content":onClick="selectDocType":text="@string/social_security"
/>
</RadioGroup>
<Button:id="@+id/continueButton":layout_width="wrap_content":layout_height="wrap_content":text="@string/continue_button":visibility="invisible":layout_marginTop="8dp":layout_constraintTop_toBottomOf="@+id/docTypes":layout_marginLeft="0dp":layout_constraintLeft_toLeftOf="@+id/docTypes":onClick="next"
/>
</android.support.constraint.ConstraintLayout>
ПРИЛОЖЕНИЕ 4
(обязательное)
Листинг макета activity_recognition.xml
<?xml
version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android":app="http://schemas.android.com/apk/res-auto":layout_width="match_parent":layout_height="match_parent":background="@android:color/black">
<Button:id="@+id/recorder_control":layout_width="64dp":layout_height="64dp":layout_alignParentBottom="true":layout_centerHorizontal="true":layout_gravity="bottom":layout_marginBottom="16dp":text="record"
/>
<demidov.docrecognition.CvCameraPreview:id="@+id/camera_view":layout_width="match_parent":layout_height="417dp":camera_type="back":scale_type="fit"
/>
</FrameLayout>
ПРИЛОЖЕНИЕ 5
(обязательное)
Листинг макета activity_document.xml
<?xml
version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android":app="http://schemas.android.com/apk/res-auto":tools="http://schemas.android.com/tools":layout_width="match_parent":layout_height="match_parent":context="demidov.docrecognition.DocumentActivity">
<Button:id="@+id/saveButton":layout_width="wrap_content":layout_height="wrap_content":layout_marginBottom="8dp":layout_marginLeft="8dp":onClick="saveData":text="@string/saveDocData":layout_constraintBottom_toBottomOf="parent":layout_constraintLeft_toLeftOf="parent"
/>
<ScrollView:id="@+id/passportVerification":layout_width="0dp":layout_height="0dp":layout_marginBottom="8dp":layout_constraintBottom_toTopOf="@+id/saveButton":layout_constraintHorizontal_bias="0.0":layout_constraintLeft_toLeftOf="parent":layout_constraintRight_toRightOf="parent":layout_constraintTop_toTopOf="parent":layout_constraintVertical_bias="0.0">
<android.support.constraint.ConstraintLayout:layout_width="wrap_content":layout_height="wrap_content":layout_editor_absoluteX="0dp":layout_editor_absoluteY="-163dp">
<TextView:id="@+id/textView":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/pass_number":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toTopOf="parent"
/>
<EditText:id="@+id/passNumber":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView"
/>
<TextView:id="@+id/textView5":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/pass_issuer_name":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/passNumber"
/>
<EditText:id="@+id/passIssuerName":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView5"
/>
<TextView:id="@+id/textView3":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/pass_issuing_date":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/passIssuerName"
/>
<EditText:id="@+id/passIssuingDate":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView3"
/>
<TextView:id="@+id/textView4":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/pass_issuer_code":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/passIssuingDate"
/>
<EditText:id="@+id/passIssuerCode":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView4"
/>
<TextView:id="@+id/textView6":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/last_name":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/passIssuerCode"
/>
<EditText:id="@+id/lastName":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView6"
/>
<TextView:id="@+id/textView7":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/first_name":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/lastName"
/>
<EditText:id="@+id/firstName":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView7"
/>
<TextView:id="@+id/textView8":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/middle_name":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/firstName"
/>
<EditText:id="@+id/middleName":layout_width="216dp":layout_height="43dp":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView8":layout_editor_absoluteX="7dp"
/>
<TextView:id="@+id/textView9":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/gender":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/middleName"
/>
<EditText:id="@+id/gender":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView9"
/>
<TextView:id="@+id/textView10":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/birth_date":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/gender"
/>
<EditText:id="@+id/birthDate":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView10"
/>
<TextView:id="@+id/textView11":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/birth_place":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/birthDate"
/>
<EditText:id="@+id/birthPlace":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginStart="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView11"
/>
</android.support.constraint.ConstraintLayout>
</ScrollView>
<ScrollView:id="@+id/snilsVerification":layout_width="0dp":layout_height="0dp":layout_marginBottom="8dp":visibility="gone":layout_constraintBottom_toTopOf="@+id/saveButton":layout_constraintHorizontal_bias="0.0":layout_constraintLeft_toLeftOf="parent":layout_constraintRight_toRightOf="parent":layout_constraintTop_toTopOf="parent":layout_constraintVertical_bias="0.0">
<android.support.constraint.ConstraintLayout:layout_width="wrap_content":layout_height="wrap_content":layout_editor_absoluteX="0dp":layout_editor_absoluteY="-163dp">
<TextView:id="@+id/textView12":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/snils_number":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toTopOf="parent"
/>
<EditText:id="@+id/snilsNumber":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView12"
/>
<TextView:id="@+id/textView13":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/last_name":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/snilsNumber"
/>
<EditText:id="@+id/snilsLastName":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView13"
/>
<TextView:id="@+id/textView14":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/first_name":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/snilsLastName"
/>
<EditText:id="@+id/snilsFirstName":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView14"
/>
<TextView:id="@+id/textView15":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/middle_name":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/snilsFirstName"
/>
<EditText:id="@+id/snilsMiddleName":layout_width="216dp":layout_height="43dp":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView15":layout_editor_absoluteX="7dp"
/>
<TextView:id="@+id/textView16":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/birth_date":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/snilsMiddleName"
/>
<EditText:id="@+id/snilsBirthDate":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView16"
/>
<TextView:id="@+id/textView17":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/birth_place":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/snilsBirthDate"
/>
<EditText:id="@+id/snilsBirthPlace":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginStart="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView17"
/>
<TextView:id="@+id/textView18":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/gender":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/snilsBirthPlace"
/>
<EditText:id="@+id/snilsGender":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView18"
/>
<TextView:id="@+id/textView19":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":text="@string/pass_issuing_date":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/snilsGender"
/>
<EditText:id="@+id/snilsIssuingDate":layout_width="wrap_content":layout_height="wrap_content":layout_marginLeft="8dp":layout_marginTop="8dp":ems="10":inputType="text":text="":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintTop_toBottomOf="@+id/textView19"
/>
</android.support.constraint.ConstraintLayout>
</ScrollView>
</android.support.constraint.ConstraintLayout>
ПРИЛОЖЕНИЕ 6
(обязательное)
Листинг макета activity_doc_list.xml
<?xml
version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android":app="http://schemas.android.com/apk/res-auto":tools="http://schemas.android.com/tools":layout_width="match_parent":layout_height="match_parent":context="demidov.docrecognition.DocListActivity">
<ListView:id="@+id/documentList":layout_width="0dp":layout_height="0dp":layout_marginBottom="8dp":layout_marginLeft="8dp":layout_marginRight="8dp":layout_marginTop="8dp":layout_constraintBottom_toBottomOf="parent":layout_constraintLeft_toLeftOf="parent":layout_constraintRight_toRightOf="parent":layout_constraintTop_toTopOf="parent":layout_marginStart="8dp":layout_marginEnd="8dp"
/>
</android.support.constraint.ConstraintLayout>
ПРИЛОЖЕНИЕ 7
(обязательное)
Листинг макета list_item.xml
<?xml
version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout:android="http://schemas.android.com/apk/res/android":app="http://schemas.android.com/apk/res-auto":tools="http://schemas.android.com/tools":layout_width="match_parent":layout_height="match_parent">
<TextView:id="@+id/textView2":layout_width="match_parent":layout_height="wrap_content":layout_marginLeft="0dp":layout_marginRight="0dp":layout_marginTop="0dp":text="TextView":textSize="18sp":layout_constraintLeft_toLeftOf="parent":layout_constraintRight_toRightOf="parent":layout_constraintTop_toTopOf="parent"
/>
</android.support.constraint.ConstraintLayout>
ПРИЛОЖЕНИЕ 8
(обязательное)
Листинг
операции LoginActivity
package demidov.docrecognition;
android.animation.Animator;
import
android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import
android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import
android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.google.common.hash.Hashing;
import demidov.docrecognition.database.DbHelper;
import
demidov.docrecognition.database.DocRecognizerContract;
public class LoginActivity extends
AppCompatActivity {
private UserLoginTask mAuthTask = null;
private AutoCompleteTextView mUsernameView;
private EditText mPasswordView;
private View mProgressView;
private View mLoginFormView;
@Override
protected void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);(R.layout.activity_login);
= (AutoCompleteTextView)
findViewById(R.id.username);
= (EditText)
findViewById(R.id.password);.setOnEditorActionListener(new
TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView
textView, int id, KeyEvent keyEvent) {
if (id == R.id.login || id ==
EditorInfo.IME_NULL) {();
return true;
}
return false;
}
});
Button signInButton = (Button)
findViewById(R.id.sign_in_button);.setOnClickListener(new
OnClickListener() {
@Override
public void onClick(View view) {();
}
});
= findViewById(R.id.login_form);=
findViewById(R.id.login_progress);
}
private void attemptLogin() {
if (mAuthTask != null) {
return;
.setError(null);.setError(null);
String username =
mUsernameView.getText().toString();
String password =
mPasswordView.getText().toString();
cancel = false;focusView = null;
if (TextUtils.isEmpty(password) ||
!isPasswordValid(password)) {.setError(getString(R.string.password_invalid));=
mPasswordView;= true;
}
if (TextUtils.isEmpty(username) ||
!isEmailValid(username)) {.setError(getString(R.string.username_invalid));=
mUsernameView;= true;
}
if (cancel) {.requestFocus();
} else {(true);= new
UserLoginTask(username, password);.execute((Void) null);
}
}
private boolean isEmailValid(String
username) {
return username.length() >= 4;
}
private boolean isPasswordValid(String
password) {
return password.length() >= 4;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
private void showProgress(final
boolean show) {
if (Build.VERSION.SDK_INT >=
Build.VERSION_CODES.HONEYCOMB_MR2) {shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
.setVisibility(show ? View.GONE :
View.VISIBLE);.animate().setDuration(shortAnimTime).alpha(? 0 : 1).setListener(new
AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation)
{.setVisibility(show ? View.GONE : View.VISIBLE);
}
});
.setVisibility(show ? View.VISIBLE :
View.GONE);.animate().setDuration(shortAnimTime).alpha(? 1 : 0).setListener(new
AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator
animation) {.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
} else {.setVisibility(show ?
View.VISIBLE : View.GONE);.setVisibility(show ? View.GONE : View.VISIBLE);
}
}
public class UserLoginTask extends
AsyncTask<Void, Void, Long> {
private final String username;
private final String password;
private final DbHelper dbHelper = new
DbHelper(LoginActivity.this);
(String username, String
password) {
this.username = username;
this.password = password;
}
@Override
protected Long doInBackground(Void...
params) {
String passwordHash =
Hashing.sha256().hashUnencodedChars(password).toString();
db = dbHelper.getWritableDatabase();
Cursor cursor =
db.query(DocRecognizerContract.User.TABLE_NAME,
new String[]{DocRecognizerContract.User._ID,
DocRecognizerContract.User.COLUMN_NAME_PASSWORD},.User.COLUMN_NAME_USERNAME +
" = ?",
new String[]{username}, null,
null, null);
userExists = cursor.moveToNext();
userId;
if (userExists) {passwordValid =
cursor.getString(1).equals(passwordHash);
if (!passwordValid) {
return null;
}= cursor.getLong(0);
} else {contentValues = new
ContentValues();.put(DocRecognizerContract.User.COLUMN_NAME_USERNAME,);.put(DocRecognizerContract.User.COLUMN_NAME_PASSWORD,);=
db.insert(DocRecognizerContract.User.TABLE_NAME, null, contentValues);
}
.close();
return userId;
}
@Override
protected void onPostExecute(final Long
userId) {= null;(false);
if (userId != null) {
((DocRecogApplication)
getApplication()).setUserId(userId);intent = new Intent(LoginActivity.this,
OperationActivity.class);(intent);
} else
{.setError(getString(R.string.error_incorrect_password));.requestFocus();
}
}
@Override
protected void onCancelled() {= null;(false);
}
}
}
ПРИЛОЖЕНИЕ 9
(обязательное)
Листинг
операции OperationActivity
package demidov.docrecognition;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import
android.support.v4.app.ActivityCompat;
import
android.support.v4.content.ContextCompat;
import
android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.RadioButton;
import android.widget.RadioGroup;
public class OperationActivity extends
AppCompatActivity {
private static final int
PERMISSIONS_REQUEST_CAMERA = 1;
private long userId;
@Override
protected void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);(R.layout.activity_operation);
}
public void selectOperation(View view)
{operation = (RadioButton) view;checked = operation.isChecked();
switch (operation.getId()) {
case R.id.createDoc:
if (checked)
{(R.id.docTypes).setVisibility(View.VISIBLE);(R.id.continueButton).setVisibility(View.INVISIBLE);
}
break;
case R.id.existingDoc:
if (checked)
{(R.id.docTypes).setVisibility(View.GONE);(R.id.continueButton).setVisibility(View.VISIBLE);
}
break;
}
}
public void next(View view) {operation = (RadioButton)
findViewById(
((RadioGroup)
findViewById(R.id.operations)).getCheckedRadioButtonId());
switch (operation.getId()) {
case R.id.createDoc:();
break;
case R.id.existingDoc:intent = new
Intent(this, DocListActivity.class);.putExtra("userId",
userId);(intent);
break;
}
}
public void selectDocType(View view)
{docType = (RadioButton) view;checked = docType.isChecked();
if (checked)
{(R.id.continueButton).setVisibility(View.VISIBLE);
}
}
private void requestPermissions() {permissionCheck
= ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
if (permissionCheck ==
PackageManager.PERMISSION_GRANTED) {();
} else {.requestPermissions(this,
new String[] {Manifest.permission.CAMERA},
PERMISSIONS_REQUEST_CAMERA);
}
}
@Override
public void onRequestPermissionsResult(int
requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
{
if (requestCode ==
PERMISSIONS_REQUEST_CAMERA) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {();
}
}
}
private void processToRecognition()
{buttonId = ((RadioGroup)
findViewById(R.id.docTypes)).getCheckedRadioButtonId();intent = new
Intent(this,
RecognitionActivity.class);.putExtra("documentType",
buttonId);.putExtra("userId", userId);(intent);
}
}
ПРИЛОЖЕНИЕ 10
(обязательное)
Листинг
операции DocumentActivity
package demidov.docrecognition;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import
android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;
import
com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import
demidov.docrecognition.database.DbHelper;
import
demidov.docrecognition.database.DocRecognizerContract;
public class DocumentActivity extends
AppCompatActivity {
private static final
Map<PassportField, Integer> passFieldsToViewIds =
ImmutableMap.<PassportField, Integer>builder()
.put(PassportField.ISSUER_NAME,
R.id.passIssuerName)
.put(PassportField.ISSUER_CODE,
R.id.passIssuerCode)
.put(PassportField.ISSUING_DATE,
R.id.passIssuingDate)
.put(PassportField.NUMBER,
R.id.passNumber)
.put(PassportField.LAST_NAME,
R.id.lastName)
.put(PassportField.FIRST_NAME,
R.id.firstName)
.put(PassportField.MIDDLE_NAME,
R.id.middleName)
.put(PassportField.GENDER,
R.id.gender)
.put(PassportField.BIRTH_DATE,
R.id.birthDate)
.put(PassportField.BIRTH_PLACE,
R.id.birthPlace)
.build();
private static final
Map<SnilsField, Integer> snilsFieldsToViewIds =
ImmutableMap.<SnilsField, Integer>builder()
.put(SnilsField.SNILS,
R.id.snilsNumber)
.put(SnilsField.LAST_NAME,
R.id.snilsLastName)
.put(SnilsField.FIRST_NAME,
R.id.snilsFirstName)
.put(SnilsField.MIDDLE_NAME,
R.id.snilsMiddleName)
.put(SnilsField.BIRTH_DATE,
R.id.snilsBirthDate)
.put(SnilsField.BIRTH_PLACE,
R.id.snilsBirthPlace)
.put(SnilsField.GENDER,
R.id.snilsGender)
.put(SnilsField.REGISTRATION_DATE,
R.id.snilsIssuingDate)
.build();
private final DbHelper dbHelper = new
DbHelper(this);
private DocumentType documentType;
private long userId;
private Map<DocumentField, String>
fields;
@Override
protected void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);(R.layout.activity_document);
bundle = getIntent().getExtras();documentFields
= bundle.getBundle("documentFields");
=
DocumentType.valueOf(bundle.getString("documentType"));=
((DocRecogApplication) getApplication()).getUserId();
Long documentId = (Long)
getIntent().getExtras().get("documentId");
readOnly = true;
if (documentId == null) {= new
HashMap<>();
if
(documentType.equals(DocumentType.PASSPORT)) {
for (String key :
documentFields.keySet()) {field = PassportField.valueOf(key);.put(field,
documentFields.getString(key));
}
} else if
(documentType.equals(DocumentType.SOCIAL_SECURITY)) {
for (String key :
documentFields.keySet()) {field = SnilsField.valueOf(key);.put(field,
documentFields.getString(key));
}
}= false;
} else {=
loadDocument(documentId);= getDocumentType(documentId);
}
if (readOnly) {(R.id.saveButton).setVisibility(View.GONE);
}
if
(documentType.equals(DocumentType.PASSPORT))
{(R.id.passportVerification).setVisibility(View.VISIBLE);(R.id.snilsVerification).setVisibility(View.GONE);
} else if
(documentType.equals(DocumentType.SOCIAL_SECURITY)) {(R.id.snilsVerification).setVisibility(View.VISIBLE);(R.id.passportVerification).setVisibility(View.GONE);
}
for (Map.Entry<DocumentField, String>
entry : fields.entrySet()) {fieldId;
if
(documentType.equals(DocumentType.PASSPORT)) {= passFieldsToViewIds.get(entry.getKey());
} else {=
snilsFieldsToViewIds.get(entry.getKey());
}textView = (EditText)
findViewById(fieldId);.setText(entry.getValue());.setFocusable(!readOnly);
}
}
public void saveData(View view) {db =
dbHelper.getWritableDatabase();
String documentType = null;
switch (this.documentType) {
case PASSPORT:=
DocRecognizerContract.DocumentType.VALUE_CODE_PASSPORT;
break;
case SOCIAL_SECURITY:=
DocRecognizerContract.DocumentType.VALUE_CODE_SNILS;
break;
}
Cursor cursor = db.query(DocRecognizerContract.DocumentType.TABLE_NAME,
new String[]{DocRecognizerContract.DocumentType._ID},.DocumentType.COLUMN_NAME_CODE
+ " = ?",
new String[] {documentType}, null,
null, null);.moveToFirst();docTypeId =
cursor.getLong(.getColumnIndexOrThrow(DocRecognizerContract.DocumentType._ID)
);.close();
gson = new
GsonBuilder().create();
String json = gson.toJson(fields);docValues
= new
ContentValues();.put(DocRecognizerContract.Document.COLUMN_NAME_DOC_TYPE_ID,
docTypeId);.put(DocRecognizerContract.Document.COLUMN_NAME_DATA,
json);.put(DocRecognizerContract.Document.COLUMN_NAME_USER_ID,
userId);.insert(DocRecognizerContract.Document.TABLE_NAME, null,
docValues);
intent = new Intent(this,
OperationActivity.class);.putExtra("userId", userId);(intent);
}
private Map<DocumentField, String>
loadDocument(long documentId) {db = dbHelper.getWritableDatabase();
Cursor cursor = db.rawQuery("SELECT
" + DocRecognizerContract.Document.TABLE_NAME + "."
+
DocRecognizerContract.Document.COLUMN_NAME_DATA + ", " +
DocRecognizerContract.DocumentType.TABLE_NAME
+ "." +
DocRecognizerContract.DocumentType.COLUMN_NAME_CODE
+ " FROM " +
DocRecognizerContract.Document.TABLE_NAME + " JOIN "
+ DocRecognizerContract.DocumentType.TABLE_NAME
+ " ON " + DocRecognizerContract.Document.TABLE_NAME
+ "." +
DocRecognizerContract.Document.COLUMN_NAME_DOC_TYPE_ID + " = "
+
DocRecognizerContract.DocumentType.TABLE_NAME + "." +
DocRecognizerContract.DocumentType._ID
+ " WHERE " + DocRecognizerContract.Document.TABLE_NAME
+ "." + DocRecognizerContract.Document._ID
+ " = ?", new String[]
{String.valueOf(documentId)});
.moveToNext();
String documentData = cursor.getString(0);
String documentType =
cursor.getString(1);.close();
type = null;
switch (documentType) {
case
DocRecognizerContract.DocumentType.VALUE_CODE_PASSPORT:= new
TypeToken<Map<PassportField, String>>(){}.getType();
break;
case
DocRecognizerContract.DocumentType.VALUE_CODE_SNILS:= new
TypeToken<Map<SnilsField, String>>(){}.getType();
break;
}
gson = new
GsonBuilder().create();
<DocumentField, String>
result = gson.fromJson(documentData, type);
return result;
}
private DocumentType getDocumentType(Long
documentId) {db = dbHelper.getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT
" + DocRecognizerContract.DocumentType.TABLE_NAME
+ "." +
DocRecognizerContract.DocumentType.COLUMN_NAME_CODE
+ " FROM " +
DocRecognizerContract.Document.TABLE_NAME + " JOIN "
+
DocRecognizerContract.DocumentType.TABLE_NAME + " ON " +
DocRecognizerContract.Document.TABLE_NAME
+ "." +
DocRecognizerContract.Document.COLUMN_NAME_DOC_TYPE_ID + " = "
+
DocRecognizerContract.DocumentType.TABLE_NAME + "." +
DocRecognizerContract.DocumentType._ID
+ " WHERE " +
DocRecognizerContract.Document.TABLE_NAME + "." +
DocRecognizerContract.Document._ID
+ " = ?", new String[]
{String.valueOf(documentId)});
documentType = null;.moveToNext();
switch (cursor.getString(0)) {
case
DocRecognizerContract.DocumentType.VALUE_CODE_PASSPORT:= DocumentType.PASSPORT;
break;
case
DocRecognizerContract.DocumentType.VALUE_CODE_SNILS:=
DocumentType.SOCIAL_SECURITY;
break;
}
return documentType;
}
}
ПРИЛОЖЕНИЕ 11
(обязательное)
Листинг
операции DocListActivity
package demidov.docrecognition;
import android.content.Intent;
import android.database.Cursor;
import
android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import
android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import
demidov.docrecognition.database.DbHelper;
import
demidov.docrecognition.database.DocRecognizerContract;
public class DocListActivity extends
AppCompatActivity {
private final DbHelper dbHelper = new
DbHelper(this);
private long userId;
@Override
protected void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);(R.layout.activity_doc_list);
listView = (ListView)
findViewById(R.id.documentList);
final SQLiteDatabase db =
dbHelper.getWritableDatabase();
final String alias =
"DOC_NAME";
final Cursor cursor =
db.rawQuery("SELECT " + DocRecognizerContract.Document.TABLE_NAME +
"."
+ DocRecognizerContract.Document._ID
+ ", " + DocRecognizerContract.DocumentType.TABLE_NAME
+ "." +
DocRecognizerContract.DocumentType.COLUMN_NAME_NAME + " || \" \"
|| "
+
DocRecognizerContract.Document.TABLE_NAME + "." +
DocRecognizerContract.Document._ID
+ " AS " + alias + "
FROM " + DocRecognizerContract.Document.TABLE_NAME + " JOIN "
+
DocRecognizerContract.DocumentType.TABLE_NAME + " ON " +
DocRecognizerContract.Document.TABLE_NAME
+ "." +
DocRecognizerContract.Document.COLUMN_NAME_DOC_TYPE_ID + " = "
+
DocRecognizerContract.DocumentType.TABLE_NAME + "." +
DocRecognizerContract.DocumentType._ID
+ " WHERE " +
DocRecognizerContract.Document.TABLE_NAME + "."
+
DocRecognizerContract.Document.COLUMN_NAME_USER_ID + " = ?", new
String[]{String.valueOf(userId)});
String[] from = {alias};[] to =
{R.id.textView2};
final SimpleCursorAdapter adapter = new
SimpleCursorAdapter(this, R.layout.list_item, cursor, from,
to);.setAdapter(adapter);
.setOnItemClickListener(new
AdapterView.OnItemClickListener() {
@Override
public void
onItemClick(AdapterView<?> parent, View view, int position, long id) {
intent = new
Intent(getApplicationContext(),
DocumentActivity.class);.putExtra("userId",
userId);.putExtra("documentId", id);
(intent);
}
});
.setOnItemLongClickListener(new
AdapterView.OnItemLongClickListener() {
@Override
public boolean
onItemLongClick(AdapterView<?> parent, View view, int position, long id)
{.delete(DocRecognizerContract.Document.TABLE_NAME,
DocRecognizerContract.Document._ID
+ " = ?", new String[]
{String.valueOf(id)});.changeCursor(db.rawQuery("SELECT " +
DocRecognizerContract.Document.TABLE_NAME + "."
+ DocRecognizerContract.Document._ID
+ ", " + DocRecognizerContract.DocumentType.TABLE_NAME
+ "." +
DocRecognizerContract.DocumentType.COLUMN_NAME_NAME + " || \" \"
|| "
+
DocRecognizerContract.Document.TABLE_NAME + "." +
DocRecognizerContract.Document._ID
+ " AS " + alias + "
FROM " + DocRecognizerContract.Document.TABLE_NAME + " JOIN "
+
DocRecognizerContract.DocumentType.TABLE_NAME + " ON " +
DocRecognizerContract.Document.TABLE_NAME
+ "." + DocRecognizerContract.Document.COLUMN_NAME_DOC_TYPE_ID
+ " = "
+
DocRecognizerContract.DocumentType.TABLE_NAME + "." +
DocRecognizerContract.DocumentType._ID, null));
return true;
}
});
}
}
ПРИЛОЖЕНИЕ 12
(обязательное)
Листинг
операции RecognitionActivity
package demidov.docrecognition;
import android.app.Activity;
import android.hardware.Camera;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import org.bytedeco.javacpp.tesseract;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import static
org.bytedeco.javacpp.opencv_core.Mat;
public class RecognitionActivity extends
Activity implements CvCameraPreview.CvCameraViewListener,
View.OnClickListener {
final String TAG =
"RecognitionActivity";
private CvCameraPreview cameraView;
private tesseract.TessBaseAPI api;
private BaseDocRecognitionService
documentExtractor;
private int documentTypeId;
@Override
protected void onCreate(@Nullable Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);
(R.layout.activity_recognition);
();= (CvCameraPreview)
findViewById(R.id.camera_view);.setCvCameraViewListener(this);(R.id.recorder_control).setOnClickListener(this);
=
getIntent().getExtras().getInt("documentType");
}
@Override
protected void onPause() {
super.onPause();
.setVisibility(View.GONE);
}
@Override
switch (documentTypeId) {
case (R.id.passport):= new
PassportRecognitionService(height, width, camera, api, getApplicationContext(),
this, cameraView);
break;
case (R.id.socialSecurity):= new
SnilsRecognitionService(height, width, camera, api, getApplicationContext(), this,
cameraView);
break;
}
}
@Override
public void onCameraViewStopped() {
}
@Override
public Mat onCameraFrame(Mat rgbaMat) {
return
documentExtractor.processPreview(rgbaMat);
}
@Override
public void onClick(View v)
{.takeFullSizePicture();
}
private void initTesseract() {
File f = new File(getCacheDir(),
"tessdata/rus.traineddata");
if (!f.exists()) try {
InputStream is =
getAssets().open("tessdata/rus.traineddata");size = is.available();[]
buffer = new byte[size];.read(buffer);.close();
.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(f);.write(buffer);.close();
} catch (Exception e) {
throw new RuntimeException(e);
}= new
tesseract.TessBaseAPI();
if (api.Init(getCacheDir().getPath(),
"rus") != 0) {.d(TAG, "Could not initialize tesseract");
}
}
}
ПРИЛОЖЕНИЕ 13
(обязательное)
Листинг
класса BaseDocRecognitionService
package demidov.docrecognition;
import android.content.Context;
import android.content.Intent;
import android.hardware.Camera;
import android.os.Bundle;
import
com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import
com.google.common.collect.SortedSetMultimap;
import
com.google.common.collect.TreeMultimap;
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacpp.opencv_core.Mat;
import
org.bytedeco.javacpp.opencv_core.MatVector;
import
org.bytedeco.javacpp.opencv_core.Rect;
import
org.bytedeco.javacv.FFmpegFrameFilter;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameFilter;
import org.bytedeco.javacv.OpenCVFrameConverter;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static
org.bytedeco.javacpp.avutil.AV_PIX_FMT_NV21;
import static
org.bytedeco.javacpp.opencv_imgproc.CHAIN_APPROX_NONE;
import static
org.bytedeco.javacpp.opencv_imgproc.CV_RGBA2GRAY;
import static
org.bytedeco.javacpp.opencv_imgproc.INTER_AREA;
import static
org.bytedeco.javacpp.opencv_imgproc.RETR_EXTERNAL;
import static
org.bytedeco.javacpp.opencv_imgproc.approxPolyDP;
import static
org.bytedeco.javacpp.opencv_imgproc.boundingRect;
import static
org.bytedeco.javacpp.opencv_imgproc.cvtColor;
import static
org.bytedeco.javacpp.opencv_imgproc.findContours;
import static
org.bytedeco.javacpp.opencv_imgproc.rectangle;
import static
org.bytedeco.javacpp.opencv_imgproc.resize;
public abstract class
BaseDocRecognitionService {
private final Camera camera;
private final Context
applicationContext;
private final Context context;
private final CvCameraPreview
cameraView;
final Map<DocumentField, Rect>
fieldsSearchingAreas;
final double previewToPictureRatio;
final Rect mask;
public BaseDocRecognitionService(int width,
int height, Camera camera, Context applicationContext, Context context,
CvCameraPreview cameraView) {
this.mask = getMask(width, height);
this.camera = camera;
this.previewToPictureRatio = ((double)
camera.getParameters().getPreviewSize().height)
/.getParameters().getPictureSize().height;
this.fieldsSearchingAreas =
calculateSearchingAreas(mask.size());
this.applicationContext =
applicationContext;
this.context = context;
this.cameraView = cameraView;
}
public Mat processPreview(Mat image)
{<DocumentField, Rect> detectedFields = detect(image);
if
(isAllFieldsFound(detectedFields.keySet())) {();
}
preview = visualizeRects(image,
detectedFields);
return preview;
}
public Multimap<DocumentField, Rect>
detect(Mat image) {croppedImage = image.apply(mask);(croppedImage,
croppedImage, CV_RGBA2GRAY);(croppedImage);(croppedImage);
return detectFields(croppedImage);
}
public Mat visualizeRects(Mat input,
Multimap<DocumentField, Rect> fields) {croppedImage =
input.apply(mask);(croppedImage, fields);(croppedImage, fieldsSearchingAreas);
return recoverToSize(input.size(), mask,
croppedImage,.bytedeco.javacpp.helper.opencv_core.AbstractScalar.WHITE);
}
private boolean isAllFieldsFound(Collection<DocumentField>
fields) {
return fields.containsAll(getAllFields());
}
takeFullSizePicture() {
try {.takePicture(null, null,
new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data,
Camera camera) {.Size pictureSize = camera.getParameters().getPictureSize();
Frame filteredFrame = filterFrame(data,
pictureSize.width, pictureSize.height);.ToMat converterToMat = new
OpenCVFrameConverter.ToMat();mat = converterToMat.convert(filteredFrame);
smallImage = new Mat();(mat,
smallImage, new opencv_core.Size(),, previewToPictureRatio, INTER_AREA);
<DocumentField, Rect>
detectedFields = detect(smallImage);<DocumentField, String>
fieldValues = recognize(mat, sortFields(detectedFields));
(fieldValues);
}
});
} catch (RuntimeException
e) {
}
}
private Frame filterFrame(byte[]
data, int width, int height) {
Frame frame = new Frame(width,
height, Frame.DEPTH_UBYTE, 2);
((ByteBuffer)
frame.image[0].position(0)).put(data);
filter = new
FFmpegFrameFilter("transpose=1,format=pix_fmts=rgba", width,
height);.setPixelFormat(AV_PIX_FMT_NV21);
Frame filteredFrame = null;
try {.start();.push(frame);=
filter.pull();.stop();
} catch (FrameFilter.Exception
e) {.printStackTrace();
}
return filteredFrame;
}
processToVerification(Map<DocumentField,
String> fieldValues) {intent = new Intent(applicationContext,
DocumentActivity.class);bundle = new Bundle();
for (Map.Entry<DocumentField, String>
entry : fieldValues.entrySet()) {.putString(entry.getKey().getName(),
entry.getValue());
}.putExtra("documentFields",
bundle);.putExtra("documentType",
getDocumentType().name());.startActivity(intent);
}
abstract Map<DocumentField, String>
recognize(Mat image, Multimap<DocumentField, Rect> fields);
abstract DocumentType getDocumentType();
scaleRect(Rect input) {x = (int)
((mask.x() + input.x()) / previewToPictureRatio);y = (int) ((mask.y() +
input.y()) / previewToPictureRatio);width = (int) (input.width() /
previewToPictureRatio);height = (int) (input.height() / previewToPictureRatio);
return new Rect(x, y, width,
height);
}
private Rect getMask(int width, int height)
{docRatio = getRatio();x = 0, y = 0;
if (((double) width)/height >
docRatio) {desiredWidth = height * docRatio;margin = (int) ((width -
desiredWidth) / 2);= margin;= (int) desiredWidth;
} else {desiredHeight = width
/ docRatio;margin = (int) ((height - desiredHeight) / 2);= margin;= (int)
desiredHeight;
}
return new Rect(x, y, width,
height);
}
abstract void findEdges(Mat image);
abstract void applyMorphology(Mat input);
private Multimap<DocumentField, Rect>
detectFields(Mat input) {<DocumentField, Rect> detectedFields =
ArrayListMultimap.create();contours = new MatVector();(input, contours,
RETR_EXTERNAL, CHAIN_APPROX_NONE);
for (int i = 0; i < contours.size();
i++) {contour = new Mat();(contours.get(i), contour, 3, true);rect
= boundingRect(contour);.x(rect.x() - 3);.y(rect.y() - 3);.width(rect.width() +
6);.height(rect.height() + 6);field = getFieldCorrespondingTo(rect);
if (field != null) {.put(field,
rect);
}
}
return detectedFields;
}
private void drawFieldRects(Mat
originalImage, Multimap<DocumentField, Rect> fields) {
for (Rect rect : fields.values())
{(originalImage, rect, new opencv_core.Scalar(255, 0, 0, 0));
}
}
private void drawSearchingAreas(Mat
originalImage, Map<DocumentField, Rect> areas) {
for (Rect rect : areas.values())
{(originalImage, rect, new opencv_core.Scalar(0, 255, 0, 0));
}
}
private DocumentField
getFieldCorrespondingTo(Rect textRect) {
for (Map.Entry<DocumentField,
Rect> entry : fieldsSearchingAreas.entrySet()) {value = entry.getValue();
if (value.contains(textRect.br())
&& value.contains(textRect.tl())) {
return entry.getKey();
}
}
return null;
}
private Mat recoverToSize(opencv_core.Size
size, Rect mask,image, opencv_core.Scalar backgroundColor) {result = new
Mat(size, image.type(), backgroundColor);.copyTo(result.apply(mask));
return result;
}
abstract double getRatio();
abstract List<DocumentField>
getAllFields();
<DocumentField, Rect>
sortFields(Multimap<DocumentField, Rect> fieldsToSort)
{<DocumentField, Rect> result = TreeMultimap.create(new
Comparator<DocumentField>() {
@Override
public int compare(DocumentField o1,
DocumentField o2) {
if (o1.getRelativeSearchingArea().y()
> o2.getRelativeSearchingArea().y()) {
return 1;
} else if
(o1.getRelativeSearchingArea().y() < o2.getRelativeSearchingArea().y()) {
return -1;
} else if
(o1.getRelativeSearchingArea().x() > o2.getRelativeSearchingArea().x()) {
return 1;
} else if
(o1.getRelativeSearchingArea().x() < o2.getRelativeSearchingArea().x()) {
return -1;
} else {
return 0;
}
}
}, new
Comparator<Rect>() {
@Override
public int compare(Rect o1, Rect o2) {
return o1.y() - o2.y();
}
});.putAll(fieldsToSort);
return result;
}
private Map<DocumentField, Rect>
calculateSearchingAreas(opencv_core.Size imageSize) {<DocumentField,
Rect> result = new HashMap<>();
for (DocumentField field :
getAllFields()) {_core.Rectd relativeSearchingArea =
field.getRelativeSearchingArea();x = (int) (imageSize.width() *
relativeSearchingArea.x());y = (int) (imageSize.height() *
relativeSearchingArea.y());width = (int) (imageSize.width() *
relativeSearchingArea.width());height = (int) (imageSize.height() *
relativeSearchingArea.height());.put(field, new Rect(x, y, width,
height));
}
return result;
}
}
ПРИЛОЖЕНИЕ 14
(обязательное)
Листинг
класса
PassportRecognitionService
package demidov.docrecognition;
import android.content.Context;
import android.hardware.Camera;
import com.google.common.collect.Multimap;
import org.bytedeco.javacpp.BytePointer;
import
org.bytedeco.javacpp.opencv_core.Mat;
import
org.bytedeco.javacpp.opencv_core.Rect;
import
org.bytedeco.javacpp.opencv_core.Size;
import org.bytedeco.javacpp.tesseract.TessBaseAPI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static
org.bytedeco.javacpp.opencv_imgproc.ADAPTIVE_THRESH_MEAN_C;
import static
org.bytedeco.javacpp.opencv_imgproc.CV_MOP_CLOSE;
import static
org.bytedeco.javacpp.opencv_imgproc.CV_MOP_OPEN;
import static
org.bytedeco.javacpp.opencv_imgproc.CV_RGBA2GRAY;
import static
org.bytedeco.javacpp.opencv_imgproc.Canny;
import static org.bytedeco.javacpp.opencv_imgproc.MORPH_ELLIPSE;
import static
org.bytedeco.javacpp.opencv_imgproc.MORPH_RECT;
import static
org.bytedeco.javacpp.opencv_imgproc.THRESH_BINARY;
import static
org.bytedeco.javacpp.opencv_imgproc.adaptiveThreshold;
import static
org.bytedeco.javacpp.opencv_imgproc.cvtColor;
import static
org.bytedeco.javacpp.opencv_imgproc.getStructuringElement;
import static
org.bytedeco.javacpp.opencv_imgproc.morphologyEx;
public class CardRecognitionService extends
BaseDocRecognitionService {
private final Size closingKernelSize;
private final Size openingKernelSize;
private final int
thresholdingKernelSize;
private final Size
recognitionClosingKernelSize;
private final TessBaseAPI
tesseractApi;
public CardRecognitionService(int width,
int height, Camera camera, TessBaseAPI tesseractApi, Context
applicationContext, Context context, CvCameraPreview cameraView) {
super(width, height, camera,
applicationContext, context, cameraView);
this.tesseractApi = tesseractApi;
fontSize = mask.height() * 14.0 /
185;
this.closingKernelSize = new
Size((int) (2 * fontSize), (int) (0.1 * fontSize));
this.openingKernelSize = new
Size((int) (0.9 * fontSize), (int) (0.9 * fontSize));
thresholdingKernelSize = (int) (2 *
fontSize / previewToPictureRatio);
this.thresholdingKernelSize =
thresholdingKernelSize % 2 == 0 ? thresholdingKernelSize + 1
: thresholdingKernelSize;
this.recognitionClosingKernelSize = new
Size((int) (0.1 * fontSize / previewToPictureRatio),
(int) (0.08 * fontSize /
previewToPictureRatio));
}
@Override<DocumentField, String>
recognize(Mat image, Multimap<DocumentField, Rect> fields)
{<DocumentField, String> recognizedFields = new
HashMap<>();(image, image, CV_RGBA2GRAY);
for (Map.Entry<DocumentField,
Rect> entry : fields.entries()) {wordRect = scaleRect(entry.getValue());word
= new Mat();.apply(wordRect).copyTo(word);
(word, word, 255,
ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, thresholdingKernelSize, 10);(word, word,
CV_MOP_CLOSE, getStructuringElement(MORPH_ELLIPSE,
recognitionClosingKernelSize));
.TesseractRect(word.data(), 1,
word.cols(), 0, 0, word.cols(), word.rows());text = tesseractApi.GetUTF8Text();
String fieldValue =
recognizedFields.get(entry.getKey());
if (text != null) {
if (fieldValue == null) {.put(entry.getKey(),
text.getString());
} else {.put(entry.getKey(),
fieldValue + " " + text.getString());
}
}
}
return recognizedFields;
}
@OverrideapplyMorphology(Mat image)
{(image, image, CV_MOP_CLOSE,(MORPH_RECT, closingKernelSize));(image, image,
CV_MOP_OPEN, getStructuringElement(MORPH_RECT, openingKernelSize));
}
@OverridegetRatio() {
return 85.6 / 53.98;
}
@Override
List<DocumentField> getAllFields()
{
return new
ArrayList<DocumentField>(Arrays.asList(CardField.values()));
}
@OverridefindEdges(Mat image)
{(image, image, CV_RGBA2GRAY);(image, image, 150, 400, 3, false);
}
@OverridegetDocumentType() {
return DocumentType.CARD;
}
}
ПРИЛОЖЕНИЕ 15
(обязательное)
Листинг
класса SnilsRecognitionService
package demidov.docrecognition;
import android.content.Context;
import android.hardware.Camera;
import com.google.common.collect.Multimap;
import org.bytedeco.javacpp.BytePointer;
import
org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacpp.opencv_core.Rect;
import
org.bytedeco.javacpp.opencv_core.Size;
import
org.bytedeco.javacpp.tesseract.TessBaseAPI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static
org.bytedeco.javacpp.opencv_imgproc.ADAPTIVE_THRESH_MEAN_C;
import static
org.bytedeco.javacpp.opencv_imgproc.CV_MOP_CLOSE;
import static
org.bytedeco.javacpp.opencv_imgproc.CV_MOP_OPEN;
import static
org.bytedeco.javacpp.opencv_imgproc.CV_RGBA2GRAY;
import static
org.bytedeco.javacpp.opencv_imgproc.Canny;
import static
org.bytedeco.javacpp.opencv_imgproc.MORPH_ELLIPSE;
import static
org.bytedeco.javacpp.opencv_imgproc.MORPH_RECT;
import static
org.bytedeco.javacpp.opencv_imgproc.THRESH_BINARY;
import static
org.bytedeco.javacpp.opencv_imgproc.adaptiveThreshold;
import static
org.bytedeco.javacpp.opencv_imgproc.cvtColor;
import static
org.bytedeco.javacpp.opencv_imgproc.getStructuringElement;
import static
org.bytedeco.javacpp.opencv_imgproc.morphologyEx;
public class SnilsRecognitionService
extends BaseDocRecognitionService {
private final Size closingKernelSize;
private final Size openingKernelSize;
private final int
thresholdingKernelSize;
private final Size
recognitionClosingKernelSize;
private final TessBaseAPI
tesseractApi;
public SnilsRecognitionService(int width,
int height, Camera camera, TessBaseAPI tesseractApi, Context
applicationContext, Context context, CvCameraPreview cameraView) {
super(width, height, camera,
applicationContext, context, cameraView);
this.tesseractApi = tesseractApi;
fontSize = mask.height() * 7.0 / 200;
this.closingKernelSize = new
Size((int) (2 * fontSize), (int) (0.1 * fontSize));
this.openingKernelSize = new
Size((int) (0.9 * fontSize), (int) (0.9 * fontSize));
thresholdingKernelSize = (int) (2 *
fontSize / previewToPictureRatio);
this.thresholdingKernelSize =
thresholdingKernelSize % 2 == 0 ? thresholdingKernelSize + 1
: thresholdingKernelSize;
this.recognitionClosingKernelSize = new
Size((int) (0.1 * fontSize / previewToPictureRatio),
(int) (0.08 * fontSize /
previewToPictureRatio));
}
@Override<DocumentField, String>
recognize(Mat image, Multimap<DocumentField, Rect> fields)
{<DocumentField, String> recognizedFields = new
HashMap<>();(image, image, CV_RGBA2GRAY);
for (Map.Entry<DocumentField,
Rect> entry : fields.entries()) {wordRect = scaleRect(entry.getValue());word
= new Mat();.apply(wordRect).copyTo(word);
(word, word, 255,
ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, thresholdingKernelSize, 10);(word, word,
CV_MOP_CLOSE, getStructuringElement(MORPH_ELLIPSE,
recognitionClosingKernelSize));
.TesseractRect(word.data(), 1,
word.cols(), 0, 0, word.cols(), word.rows());text = tesseractApi.GetUTF8Text();
String fieldValue =
recognizedFields.get(entry.getKey());
if (text != null) {
if (fieldValue == null)
{.put(entry.getKey(), text.getString());
} else {.put(entry.getKey(),
fieldValue + " " + text.getString());
}
}
}
return recognizedFields;
}
@OverrideapplyMorphology(Mat image)
{(image, image, CV_MOP_CLOSE,(MORPH_RECT, closingKernelSize));(image, image,
CV_MOP_OPEN, getStructuringElement(MORPH_RECT, openingKernelSize));
}
@OverridegetRatio() {
return 85.0/55;
}
@Override
List<DocumentField> getAllFields()
{
return new
ArrayList<DocumentField>(Arrays.asList(SnilsField.values()));
}
@OverridefindEdges(Mat image)
{(image, image, 100, 250, 3, false);
}
@OverridegetDocumentType() {
return DocumentType.SOCIAL_SECURITY;
}
}
ПРИЛОЖЕНИЕ 16
(обязательное)
Листинг
класса DocumentField
package demidov.docrecognition;
import org.bytedeco.javacpp.opencv_core;
public interface DocumentField {
String getName();
_core.Rectd
getRelativeSearchingArea();
}
ПРИЛОЖЕНИЕ 17
(обязательное)
Листинг
класса PassportField
package demidov.docrecognition;
import org.bytedeco.javacpp.opencv_core;
public enum PassportField implements
DocumentField {
_NAME("Кем выдан", new
opencv_core.Rectd(29.0/426, 40.0/600, 371.0/426, 85.0/600)),_DATE("Дата
выдачи", new opencv_core.Rectd(29.0/426, 110.0/600, 154.0/426,
40.0/600)),_CODE("Код подразделения", new
opencv_core.Rectd(187.0/426, 110.0/600, 209.0/426,
40.0/600)),_NAME("Фамилия", new opencv_core.Rectd(149.0/426,
341.0/600, 216.0/426, 45.0/600)),_NAME("Имя", new opencv_core.Rectd(149.0/426, 380.0/600,
239.0/426, 30.0/600)),_NAME("Отчество", new
opencv_core.Rectd(149.0/426, 405.0/600, 239.0/426, 30.0/600)),("Пол",
new opencv_core.Rectd(149.0/426, 430.0/600, 68.0/426,
30.0/600)),_DATE("Дата рождения", new opencv_core.Rectd(241.0/426,
430.0/600, 147.0/426, 30.0/600)),_PLACE("Место рождения", new
opencv_core.Rectd(149.0/426, 455.0/600, 246.0/426, 80.0/600)),("Номер
паспорта", new opencv_core.Rectd(390.0/426, 63.0/600, 30.0/426,
167.0/600));
private String fieldName;
private opencv_core.Rectd
relativeSearchingArea;
(String fieldName,
opencv_core.Rectd relativeSearchingArea) {
this.relativeSearchingArea =
relativeSearchingArea;
}
public String getName() {
return this.name();
}
@Override
public opencv_core.Rectd
getRelativeSearchingArea() {
return relativeSearchingArea;
}
}
ПРИЛОЖЕНИЕ 18
(обязательное)
Листинг
класса SnilsField
package demidov.docrecognition;
import org.bytedeco.javacpp.opencv_core;
public enum SnilsField implements
DocumentField {
("СНИЛС", new
opencv_core.Rectd(53.0/300, 47.0/200, 166.0/300,
29.0/200)),_NAME("Фамилия", new opencv_core.Rectd(45.0/300,
71.0/200, 141.0/300, 20.0/200)),_NAME("Имя", new opencv_core.Rectd(45.0/300, 83.0/200, 141.0/300,
20.0/200)),_NAME("Отчество", new opencv_core.Rectd(45.0/300,
94.0/200, 141.0/300, 20.0/200)),_DATE("Дата рождения", new
opencv_core.Rectd(115.0/300, 100.0/200, 161.0/300,
30.0/200)),_PLACE("Место рождения", new
opencv_core.Rectd(17.0/300, 121.0/200, 180.0/300, 45.0/200)),("Пол", new
opencv_core.Rectd(35.0/300, 160.0/200, 64.0/300, 20.0/200)),_DATE("Дата
регистрации", new opencv_core.Rectd(90.0/300, 175.0/200, 112.0/300,
20.0/200));
private String fieldName;
private opencv_core.Rectd
relativeSearchingArea;(String fieldName, opencv_core.Rectd
relativeSearchingArea) {
this.fieldName = fieldName;
this.relativeSearchingArea =
relativeSearchingArea;
}
public String getName() {
return this.name();
}
@Override
public opencv_core.Rectd
getRelativeSearchingArea() {
return relativeSearchingArea;
}
}
ПРИЛОЖЕНИЕ 19
(обязательное)
Листинг
класса DocRecognizerContract
package demidov.docrecognition.database;
import android.provider.BaseColumns;
public final class
DocRecognizerContract {
private DocRecognizerContract() {
}
public static class Document implements
BaseColumns {
public static final String
TABLE_NAME = "DOCUMENT";
public static final String
COLUMN_NAME_DOC_TYPE_ID = "DOC_TYPE_ID";
public static final String
COLUMN_NAME_USER_ID = "USER_ID";
public static final String
COLUMN_NAME_INSERT_DATE = "INSERT_DATE";
public static final String
COLUMN_NAME_DATA = "DATA";
}
public static class
DocumentType implements BaseColumns {
public static final String
TABLE_NAME = "DOCUMENT_TYPE";
public static final String
COLUMN_NAME_CODE = "CODE";
public static final String
COLUMN_NAME_NAME = "NAME";
public static final String
VALUE_CODE_PASSPORT = "PASSPORT";
public static final String
VALUE_CODE_SNILS = "SNILS";
public static final String
VALUE_NAME_PASSPORT = "Паспорт";
public static final String
VALUE_NAME_SNILS = "СНИЛС";
}
public static class User implements
BaseColumns {
public static final String
TABLE_NAME = "USER";
public static final String
COLUMN_NAME_USERNAME = "USERNAME";
public static final String
COLUMN_NAME_PASSWORD = "PASSWORD";
}
}
ПРИЛОЖЕНИЕ 20
(обязательное)
Листинг
класса DbHelper
package demidov.docrecognition.database;
import android.content.ContentValues;
import android.content.Context;
import
android.database.sqlite.SQLiteDatabase;
import
android.database.sqlite.SQLiteOpenHelper;
import static
demidov.docrecognition.database.DocRecognizerContract.DocumentType.COLUMN_NAME_CODE;
import static
demidov.docrecognition.database.DocRecognizerContract.DocumentType.COLUMN_NAME_NAME;
import static
demidov.docrecognition.database.DocRecognizerContract.DocumentType.VALUE_CODE_PASSPORT;
import static
demidov.docrecognition.database.DocRecognizerContract.DocumentType.VALUE_CODE_SNILS;
import static
demidov.docrecognition.database.DocRecognizerContract.DocumentType.VALUE_NAME_PASSPORT;
import static
demidov.docrecognition.database.DocRecognizerContract.DocumentType.VALUE_NAME_SNILS;
public class DbHelper extends
SQLiteOpenHelper {
private static final int
DATABASE_VERSION = 3;
private static final String
DATABASE_NAME = "DocRecognizer.db";
private static final String
SQL_CREATE_DOCUMENT =
"CREATE TABLE " +
DocRecognizerContract.Document.TABLE_NAME + " (" +.Document._ID +
" INTEGER PRIMARY KEY," +.Document.COLUMN_NAME_INSERT_DATE + "
TEXT," +.Document.COLUMN_NAME_DATA + " TEXT,"
+.Document.COLUMN_NAME_USER_ID + " INTEGER,"
+.Document.COLUMN_NAME_DOC_TYPE_ID + " INTEGER)";
private static final String
SQL_CREATE_DOCUMENT_TYPE =
"CREATE TABLE " +
DocRecognizerContract.DocumentType.TABLE_NAME + " (" +.DocumentType._ID
+ " INTEGER PRIMARY KEY," +.DocumentType.COLUMN_NAME_NAME + "
TEXT," +.DocumentType.COLUMN_NAME_CODE + " TEXT)";
private static final String
SQL_CREATE_USER =
"CREATE TABLE " +
DocRecognizerContract.User.TABLE_NAME + " (" +.User._ID + " INTEGER
PRIMARY KEY," +.User.COLUMN_NAME_USERNAME + " TEXT,"
+.User.COLUMN_NAME_PASSWORD + " TEXT)";
private static final String
SQL_DROP_DOCUMENT =
"DROP TABLE IF EXISTS " +
DocRecognizerContract.Document.TABLE_NAME;
private static final String
SQL_DROP_DOCUMENT_TYPE =
"DROP TABLE IF EXISTS " +
DocRecognizerContract.DocumentType.TABLE_NAME;
private static final String
SQL_DROP_USER =
"DROP TABLE IF EXISTS " +
DocRecognizerContract.User.TABLE_NAME;
public DbHelper(Context context) {
super(context, DATABASE_NAME, null,
DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db)
{.execSQL(SQL_CREATE_DOCUMENT);.execSQL(SQL_CREATE_DOCUMENT_TYPE);.execSQL(SQL_CREATE_USER);
docTypeValues = new
ContentValues();.put(COLUMN_NAME_CODE, VALUE_CODE_PASSPORT);.put(COLUMN_NAME_NAME,
VALUE_NAME_PASSPORT);.insert(DocRecognizerContract.DocumentType.TABLE_NAME, null,
docTypeValues);= new ContentValues();.put(COLUMN_NAME_CODE,
VALUE_CODE_SNILS);.put(COLUMN_NAME_NAME, VALUE_NAME_SNILS);.insert(DocRecognizerContract.DocumentType.TABLE_NAME,
null, docTypeValues);
}
@Override
public void onUpgrade(SQLiteDatabase db,
int oldVersion, int newVersion)
{.execSQL(SQL_DROP_DOCUMENT);.execSQL(SQL_DROP_DOCUMENT_TYPE);.execSQL(SQL_DROP_USER);(db);
}
@Override
public void onDowngrade(SQLiteDatabase db,
int oldVersion, int newVersion) {(db, oldVersion, newVersion);
}
}
ПРИЛОЖЕНИЕ 21
(обязательное)
Листинг
класса DocRecogApplication
package demidov.docrecognition;
import android.app.Application;
public class DocRecogApplication extends
Application {
private Long userId;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}
ПРИЛОЖЕНИЕ 22
(обязательное)
Листинг
класса DocumentType
package demidov.docrecognition;
public enum DocumentType {
,_SECURITY
}
ПРИЛОЖЕНИЕ 23
(обязательное)
Листинг файла конфигурации Gradle build.gradle
apply plugin: 'com.android.application'
{25"25.0.2"{"demidov.docrecognition"2125true1"1.0""android.support.test.runner.AndroidJUnitRunner"
}{{falsegetDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
{fileTree(dir: 'libs', include:
['*.jar'])('com.android.support.test.espresso:espresso-core:2.2.2', {group:
'com.android.support', module: 'support-annotations'group:
'com.google.code.findbugs'
})group: 'org.bytedeco', name: 'javacv', version:
'1.3.2'group: 'org.bytedeco.javacpp-presets', name: 'opencv', version:
'3.2.0-1.3', classifier: 'android-arm'group: 'org.bytedeco.javacpp-presets',
name: 'ffmpeg', version: '3.2.1-1.3', classifier: 'android-arm'group:
'org.bytedeco.javacpp-presets', name: 'tesseract', version: '3.04.01-1.3'group:
'org.bytedeco.javacpp-presets', name: 'tesseract', version: '3.04.01-1.3',
classifier: 'android-arm'group: 'org.bytedeco.javacpp-presets', name:
'leptonica', version: '1.73-1.3', classifier:
'android-arm''com.android.support:appcompat-v7:25.3.1''com.android.support.constraint:constraint-layout:1.0.2''com.android.support:design:25.3.1''com.google.guava:guava:22.0-android''com.google.code.gson:gson:2.8.0''junit:junit:4.12'
}