Добрый день! Меня зовут Дмитрий Воронцов, я автор андроид фреймворка Simple о котором писал тут. Представляю большое обновление CV-возможностей в новом релизе платформы 14. Теперь ActveCV существует не параллельно разработке на обычных экранах, а является элементом экрана, таким, как например кнопка или надпись. Это не просто подход к дизайну, а совершенно другие возможности работы – совмещение логики экранов и обработки видеопотока.

Что такое ActiveCV?

Примеры обновленного ActiveCV
Примеры обновленного ActiveCV

Так я назвал не технологию, а скорее методологию автоматизации бизнес-процесса, когда все необходимые данные бизнес-процесса выводятся не на экран, а сразу в видеопоток, при этом с камерой работают различные детекторы: штрихкодов, OCR, лиц и т.д. Также смысл сего действия в беспрерывной работе оператора без необходимости каких то переключений. Например, запустил ActiveCV – отсканировал код помещения, не прерываясь переключился на сканирование инвентарных кодов оборудования, VIN-ов, далее серии фото этого оборудования и все это не переключаясь на обычные экраны с кнопками. И пошел дальше к другим объектам.  Т.е. это упрощенно «рисовалка бизнес-данных в окошке предпросмотра». Но это имеет эффект. По прошествии 5 лет внедрений ActiveCV на предприятиях разного профиля накопился опыт и оказалось, что некоторые подходы выстрелили а некоторые оказались не нужные. Например, однозначно можно сказать что самая важная часть ActiveCV это даже не текст над объектами – это цветовая маркировка!

Примеры цветовой маркировки:

  • объект находится там, где надо — зеленый цвет, там, где не надо — красный.

  • объект проинвентаризирован — зеленый цвет, не проинвентаризирован — желтый.

  • заказ просрочен по дедлайну — красный, подходит срок — желтый, не просрочен — зеленый

Последний пример мы называем «светофор», он вообще часто используется. В итоге именно цветовая маркировка – наибыстрейший способ донести информацию до оператора, текст часто вообще не читают.

Но что-то показало себя плохо – например «тач по объектам» оказался просто неудобен. Я сознательно не включил его в новую генерацию ActiveCV. Не то чтобы это не работает, просто неудобно ловить объекты на экране. Старайтесь продумывать схемы работы так, чтобы не делать этого. Тем более теперь объекты же можно выводить на экран и «тачить» уже в обычные списки и кнопки.

Коротко о нововведениях

Прежде чем перейти к детальному разбору, короткий перечень основных нововведений:

  • Как уже было упомянуто – теперь ActiveCV обычный элемент экрана, который имеет размеры, привязку. Его можно разместить на часть экрана, на весь экран. Но главное – он является частью логики экранов, т.е. все команды, обработчики – экранные. Совершенно другие сценарии использования вырисовываются!

  • Теперь можно совмещать одновременно несколько детекторов, переключать детекторы и их настройки плавно (без перезагрузки). Детекторы подключаются слоями – можно подключить сколько угодно (на текущий момент доступно только BARCODE, OCR и PHOTO) но скоро будут и другие. Теперь нет концепции «шагов» - концепция размыта, шаги определяются логикой в обработчиках. По «плавным переключением» имеется ввиду что нет никакой переинициализации камеры (!) т.е. нет задержи с морганием и перефокусировкой, подачей напряжения – процесс абсолютно бесшовный. Это касается как типов детекторов так и их настроек.

  • Так как все  настройки теперь через код, а не конструктор то и возможностей побольше. Например, можно устанавливать типы штрихкодов и переключать доступные типы штрихкодов на лету. Зачем это нужно? Во-первых, это ускоряет процесс считывания – когда детектор ищет только определенные типы штрихкодов. Во-вторых, при сканировании товара например надо сначала искать EAN-производителя, а когда нашел переключаться на DataMatrix. Теперь это можно.

  • OCR вообще переработан. Это касается как опорного датасета так и настроек. Раньше поиск производился по SQL (а что было делать тем, кто не использует SQL?) по списку через переменную стека (а что делать если у тебя 100 000 объектов?) либо обрабатывался в обработчике (но это же долго, да?). Теперь вместо этого единый подход – новый механизм датасетов. Раньше были настройки «распознавание дат», «чисел» и т.д. Теперь единый подход – Regex-маски. Это и для обычных артикулов/инвентарных номеров и для форматного сканирования. Также добавилась настраиваемая предобработка и постобработка.

  • Датасеты работают валидаторами для всех детекторов, они же – источник информации об объектах. Это нативный инструмент, который работает мгновенно, в отличии от python-обработчиков и тем более онлайн-обработчиков, что позволяет разгрузить обработчики и сделать процесс более плавным.

  • Теперь можно ставить на паузу камеру. Не то, чтобы это было необходимо, но добавляет драматичности происходящему – как бы говорит «всё, мы нашли то что нужно, переходим к следующему шагу»

  • Теперь нет жестких списков – красный, желтый зеленый. Список один и цвет можно назначать в HEX-формате, т.е. любой. Цвет надписей подбирается теперь автоматически под интенсивность фона.

  • По сути новый элемент – это  замена экранным элементам Штрихкод (камера), Фото и Распознавание текста. Они больше не нужны как отдельные элементы.

  • Теперь есть управление зумом. Это важно если сканируемые предметы находятся на отдалении от оператора.

  • Теперь есть управление фонариком камеры.

  • Ну и самое главное - значительно повысилась производительность всего - и отображения и детекторов.

Несколько примеров на новом модуле.

Прежде чем перейти к детальному описанию системы команд и настроек, покажу обзор примеров. Все примеры доступны в тестовой конфе, которую можно скачать тут. Ее можно открыть в онлайн-редакторе https://seditor.ru:1555/ (либо в локальной версии этого конструктора) и запустить на своем телефоне.

Для примеров нам понадобятся тестовые данные. Их можно либо заполнить через процесс Заполнение БД (данные хранятся локально в NoSQL Pelican) либо добавить через код в обработчике onLaunch (в Общие обработчики) . Либо, для тех кто не планирует использовать Python, а планирует решение только онлайн (на 1С например) есть пример процесса как заполнять датасеты через команды стека переменных (в свою очередь предполагается что в стек команды и данные заполняются в онлайн-обработчиках на стороне вашей системы).

 Кстати, не удивляйтесь что появилась такая неизвестная сущность как «датасеты». Вы не пропустили релиз, я о них еще не писал. Более того, они еще не дописаны, но это будет механизм, на котором работает много чего, в т.ч. OCR и валидаторы, поэтому частично они представлены в этом релизе. Он них позже.

Дискретный вариант  «Сбор остатков».

Тут и штрихкоды и OCR (в видео только OCR, штрихкоды аналогично). Объект валидируется – ищутся только те объекты, которые есть в базе. Если найден, то видеопоток ставится на паузу а распознанный объект добавляется в таблицу на экране. Где с ним можно провести уточняющие действия – ввести остаток на складе. Это пример организации процесса когда требуется вводить доп. информацию. Можно конечно разнести это на несколько экранов, но можно и так. В процессе можно переключиться в режим просто просмотра введенной информации.

Для OCR валидация с опорным датасетом очень важна. Во первых это быстро – на обработчик не вываливается весь текст а только нужный, во вторых найденный артикул проверяется т.е. достигается необходимая точность.

 Покажу еще тот же процесс, но с другими «заковыристыми» текстовыми идентификаторами – выштамповкой на металле. Тут нет никакого монтажа – оно реально так быстро работает.

 

Показ «остатков на складе», идентификация по штрихкоду и по артикулу(OCR)

Простой процесс. Просто показывается информация об объектах из БД и их остатке в видеопотоке. Красным – если объекта нет в базе.

Работа с датами в двух примерах

Эти процессы демонстрируют как OCR работает без валидации, но с жесткими Regex-масками.

Примеры очень локальные, в реальной жизни и масок будет больше и алгоритм сильно разнообразнее.

Пример 1. Проверка "просрочки" по товару с известным сроком годности.

Задача:

  1. распознать товар по штрихкоду

  2. посмотреть в базе срок годности этого товара в днях *В примере я не ищу товар по базе и не смотрю срок годности, потому что у меня нет базы. Я просто пишу «Торт наполеон», а срок годности – константа, 4 дня.

  3. Найти на упаковке дату производства

  4. Если дата производства+ срок годности в днях < текущего дня, то покрасить этот товар красным и голосом сообщить о просрочке

На видео процесс происходит слишком быстро...
На видео процесс происходит слишком быстро...
на стоп-кадрах видно работу алгоритма.
на стоп-кадрах видно работу алгоритма.

Я разобрал видео по шагам на стоп-кадрах и поясню процесс реализации:

1)      Режим BARCODE – ищем штрихкод. Как находим, сразу же переключаемся на OCR. В реальной жизни тут должен быть поиск по базе, но к меня урощено.

2)      В OCR ищем дату. В видео это мелькание происходит так быстро, что даже незаметно, но на стоп-кадре видно край рамки. На этом шаге анализируем дату и если она просрочена, то красим в красный и заодно показываем до какой даты был срок годности.

3)      Снова переключаемся на BARCODE и заодно подсвечиваем штрихкоды с просрочкой

Этот пример я сделал чтобы показать как быстро работает переключение детекторов в новом ActiveCV Все вместе это занимает менее 1 секунды и даже на слабом телефоне работает очень быстро.

Обработчик onStart где формируются настройки:

Скрытый текст
hashMap.put("CameraSetResolutionAnalysis","640")
hashMap.put("CameraSetDetector","BARCODE") #начинаем с штрихкода
hashMap.put("action","Сканируйте штрихкод")
hashMap.put("CameraSetPrettyView","")

Обработчик события штрихкода:

Скрытый текст
jbarcodes = json.loads(hashMap.get("detected_values")) #получам массив штрихкодов
if hashMap.containsKey("SetObjectsView"):
	objects = json.loads(hashMap.get("SetObjectsView"))
else:	
	objects = []

#тут конечно же надо искать штрих-код по базе, но суть примера от этого не меняется	
current_barcode=""
for item in jbarcodes:
	current_barcode = 		item["value"]
	objects.append({"id":item["value"],"color":"#c035cb","caption":"Торт Наполеон домашний"})

#Теперь переключаем дететктор в режим сканирования дат
hashMap.put("CameraSetDetector","OCR") #теперь детектор - OCR и зададим маски дат
hashMap.put("CameraSetOCRMask",json_to_str(["(\d{2}[.]\d{2}[.]\d{4})","(^\d{2}[.][\s]\d{2}[.][\s]\d{2}$)","(\d{2}[.]\d{2}[.]\d{2})","(\d{2}[-]\d{2}[-]\d{4})"]))
hashMap.put("CameraSetOCRFormatOptions","TOZERO_CLEARSPACES")
hashMap.put("current_barcode",current_barcode)
hashMap.put("SetObjectsView",json_to_str(objects)) 
hashMap.put("noRefresh","") #нет - обновлению экрана
hashMap.put("action","Ищите дату производства")
hashMap.put("UpdateLayout","upd") #перерисуем надпись без обновления экрана

Обработчик события текста:

Скрытый текст
import datetime

today_=datetime.datetime.now().date()

jbarcodes = json.loads(hashMap.get("detected_values"))
if hashMap.containsKey("SetObjectsView"):
	objects = json.loads(hashMap.get("SetObjectsView"))
else:	
	objects = []

STORAGE_PERIOD = 4	#взято просто так, в реале - читать из СУБД
	
if len(jbarcodes)>0:
	strdate = jbarcodes[0]["value"].replace(" ","")
	d=None
	try:            
		if len(strdate)>8:
			d = 	datetime.datetime.strptime(strdate , "%d.%m.%Y").date() #тут парсер для конкретного формата, это упрощенно
		else:
			d = 	datetime.datetime.strptime(strdate, "%d.%m.%y").date() #тут парсер для конкретного формата, это упрощенно
	except:
		pass
	if d!=None:
		if d.year>1979 and d.year<2055:
			beep()
			maxdate = d+datetime.timedelta(days=STORAGE_PERIOD)
			if maxdate < today_: 
				speak("Обнаружена просрочка")
				objects.append({"id":hashMap.get("current_barcode"),"color":"#dc0e0e","caption":"Торт Наполеон. Годен до: "+maxdate.strftime("%d.%m.%y")})
				
			else:
				objects.append({"id":jbarcodes[0]["value"],"color":"#3cb552","caption":"Торт Наполеон. Годен до: "+maxdate.strftime("%d.%m.%y")})
				

			hashMap.put("SetObjectsView",json_to_str(objects)) 
			hashMap.put("CameraSetDetector","BARCODE") 
			hashMap.put("noRefresh","")
			hashMap.put("action","Сканируйте штрихкод")
			hashMap.put("UpdateLayout","upd")

Пример 2. Поиск двух дат в кадре.

Задача: найти на упаковке 2 даты (дата производства и срок годности) с проверить срок годности.

Тут для примера OCR должен найти в кадре 2 даты одновременно, найти среди них большую дату и сравнить с текущим днем. Если она меньше – то красим в красный и голосом говорим что товар просрочен, если нет то красим в зеленый. Допущения тут на каждом шагу – маски настроены на определенный формат, парсинг – тоже на определенный формат.

Тут используются такие механизмы как отключение автоматической регистрации новых объектов (так как даты могут отсканироваться друг за другом а не одновременно в одном событии, то регистрация новых добавляется вручную), валидация на дату в предобработке и Regex-маски дат.

Штрихкоды: потоковое сканирование в таблицу

Просто новые штрихкоды добавляются в табличку на экране. Такой себе сборщик новых штрихкодов в непрерывном цикле. Обработчик для новых штрихкодов вызывается просто для того чтобы покрасить их в другой цвет и добавить в таблицу.

Некоторые другие возможности

Тут демонстрируется: включение подсветки, зум, переключение с DataMatrix на EAN13.

Просто фотографирование, и фотографирование вместе с детектором

Режим фото подключается как обычный детектор и может сочетаться с другими детекторами.

Тут просто фото в галерею. Как и раньше можно фоткать в файлы и получать ссылку на файл (флаг mm_local) а можно в base64(тормозной вариант).

А вот пример, когда в обработчике new_barcodes_detected  вызывается команда CameraImageCapture которая делает снимок сразу как отсканирован штрихкод и помещает в галерею. Можно сделать в таблицу если есть желание.

Обзор механизмов работы.

Общее

Размещение визуального элемента.

Размещение на экране ничем не отличается от других элементов, но как и например HTML-поле требует чтобы была отключена прокрутка корневого контейнера. Сам визуальный элемент называется ActiveCV

Разрешение

Можно задавать разрешение для детектора CameraSetResolutionAnalysis и для фото CameraSetResolutionImage. Разрешение предпросмотра меняться не будет – оно подстраивается автоматически.

Цикл работы детекторов. Общее.

Детектор включается/переключается командой CameraSetDetector, где параметром указывается тип или типы детекторов. Сейчас доступны BARCODE, OCR и PHOTO. Если нужно совместить несколько – то через нижнее подчеркивание. Например BARCODE_PHOTO

Когда появляется новый объект в кадре, который еще не был распознан срабатывает событие new_text_detected или new_barcodes_detected в зависимости от детектора. В стеке доступна строка с JSON-массивом объектов кадра. Наполнение распознанных элементов зависит от детектора. В обработчике этого события возможно задать внешний вид распознанных объектов.

Отображение объектов

Можно переопределять заголовки распознанных объектов и задавать цвет рамки. Это все хранится в одном списке SetObjectsView в виде JSON-массива объектов с полями id, color (HEX-формат) и caption. Id – это соответственно штрихкод или текст.

Для медленных устройств отображение упрощенное. Для быстрых доступна HTML-строки в заголовках объектов. . Т.е. можно например написать в caption «Товар такой то, <b> остаток такой то </b>». Также в PrettyView секциф заголовка выстраивается по размеру объекта а не текста, т.е. происходят переносы. Включается этот режим командой  CameraSetPrettyView

Ручное управление списком детектированных объектов.

По умолчанию на новые объекты вызывается обработчик new_..._detected и после этого они уже перестают считаться «новыми», на них события не вызываются. Но можно управлять этим вручную с помощью флага CameraSetOCRDetectedListManual, пустой параметр, затем ручная регистрация с помощью CameraOCRAddDetected, параметр – список ID. Также доступно CameraClearDetected, пустой параметр для того, чтобы просто сбросить список всех детектированных объектов.

Подключение валидатора

Принцип работы один для все детекторов, но вызывается разными командами, чтобы совместить в одном датасете поля, подлежащие валидации. Например, в примерах в одном датасете артикул и штрихкод. Для штрихкодов команда CameraSetBarcodeValidator для OCR CameraSetOCRValidator

В качестве параметра – объект типа {"dataserver":<имя датасета>,"keys":[массив имен hash-индексов]} Записи в датасете имеют поле _Id (если оно не задано, то задается автоматически, но можно записывать сразу с _id) – это первый индекс. Также можно добавить хаш-индексы для любых полей в настроки датасете при создании. Например CreateDataSet("goods",json_to_str({"hash_keys":["article","barcode"]}))

По этим полям и будет производиться поиск и валидация. Когда подключен валидатор, то события возникают только если запись найдена в валидаторе. Также в случае с валидатором при нахождении записи в выдаваемое значение будет добавлена сама запись датасета целиков в поле result.

 Зум

CameraSetZoom, параметр – число требуемого приближения (стек перменных строковый, поэтому и числа и другие параметры в виде строки).

Остановка видеопотока.

 CameraStopDetectorOnNewObjects - включение режима, когда предпросмотр камеры встает на паузу автоматически при обнаружении объекта.

Альтернатива – использование из кода обработчика команды CameraStop.

Возобновляется – обновлением экрана.

Фонарик

CameraTorchTurnOn – включает подсветку камеры (если есть аппаратная возможность)

 Особенности детектора штрихкодов

CameraSetSupportedBarcodes задает список доступных штрихкодов через нижнее подчеркивание. Например: hashMap.put("CameraSetSupportedBarcodes","QR_EAN13")

Если не задано, либо задано ALL то сканируются все.

Список доступных форматов: QR, EAN13, AZTEC, CODABAR, CODE_93, CODE_39, CODE_128, DATA_MATRIX, EAN_8, ITF, UPC_A, UPC_E

 CameraSetCurrentBarcodeDetector  задает список текущих форматов штрихкодов при динамическом переключении. Формат аналогичен CameraSetSupportedBarcodes. При этом

CameraSetSupportedBarcodes задает форматы которые камера вообще способна считывать. Это так сказать – для ускорения работы и отсечки возможных ошибок. А CameraSetCurrentBarcodeDetector   для переключения между форматами в процессе работы.

 Массив штрихкодов в detected_values включает в себя объекты с полями: value – штрихкод как есть (со спецсимволами если они есть), display_value – отображаемое значение, format – формат штрихкода. Ну и result если используется валидатор.

 Особенности OCR

Цикл обработки блоков текста включает в себя несколько этапов. Все они происходят очень быстро так как выполняются платформой. Поэтому крайне рекомендую не отдавать в обработчики сырой текст, пропущенный через слабые фильтры и обрабатывать его как есть там – это будет гораздо более тормозящий вариант чем использование масок, валидаторов и предобработки.

Обработка текста в ActiveCV
Обработка текста в ActiveCV

Итак, текст может быть подвергнут предобработке, после чего к нему применяются Regex-маски, после чего могут выполниться еще процедуры предобработки (часть настроек работает до масок- часть после), после чего он либо попадает на валидатор либо отдается в обработчик new_text_detected как есть. Если задача к примеру выделить все даты в кадре то валидатор не нужен, а если сверить инвентарные номера - то подключаем валидатор.

Команда CameraSetOCRFormatOptions задает опции предобработки текста. Она может включать в себя несколько действий через нижнее подчеркивание:

  • CLEARSPACES – убирает различные пробелы

  • LOWER -преобразует к нижнему регистру

  • UPPER – преобразует к верхнему регистру

  • TOZERO – преобразует букву О в ноль

И часть опций, которая выполняется уже после отбора Regex:

DATE, INT, FLOAT – нативная проверка текста на соответствующий тип

Команда CameraSetOCRMask – задает JSON массив строк-масок. Каждая  маска представляет из себя Regex-выражение. Например, "([a-zA-Z0-9-.]{5,10})" - это маска, для поиска подстрок включающих в себя символы латинского алфавита и цифры общей длиной от 5 до 10 символов. Удобно проверять маски через редакторы regex-выражений, например https://regex101.com/ Каждая маска последовательно применяется, приоритет имеет та, которая стоит раньше в массиве.

 CameraOCRListOnly флаг чтобы выводились не только текст после валидатора, если он есть.

 detected_values в OCR содержат в себе поля:

  • value - текст после всех преобразований

  • confidence - точность определения

  • result - запись валидатора

Изменения в прочих методах платформы версии 14, не относящие к ActiveCV

1.       UpdateLayout перерисовывает контейнер с выбранной переменной, используя текущие переменные стека. Т.е. это аналог RefreshScreen но для одного контейнера. Рекомендуется использовать вместе с ActiveCV, чтобы избежать перезагрузки камеры и сделать работу плавной. Для этого также в любых обработчиках надо добавлять onRefresh чтобы не происходила перерисовка всего экрана

2.       ShowDialogListener – определяет listener события по закрытию диалога

3.       Работа с датасетами. Работает как из python-обработчиков, так и через стек-переменных. Часть данной темы, относящаяся к ActiveCV - это использование готового датасета в валидаторе, описанное выше. Перед использованием валидатора датасет должен быть создан и заполнен.

Итог

Это начало большой реформации CV-возможностей, тут далеко не все из того, что есть даже в первой ActiveCV и тем более далеко не все из того что будет. Например из того что напрашивается в первую очередь:

  • Рамки для "прицела" OCR. В первой генерации рамки были из встроенного в SimpleUI векторного редактора, но получил негативный фидбек от разработчиков, поэтому переделаю. Будет определяться из кода - просто условные координаты областей для считывания с какими то подписями

  • Использование не только ML от Google но и OpenCV,PyTorch,OCR на Python. Ведь python не даром в Simple - нужно пользоваться возможностями. Ни один из OCR которые существуют не умеет нормально работать с точечной печатью (например срок годности на бутылке с молоком). Но это решаемая проблема за счет моделей детекции и распознавания обученных на таких датасетах.

  • В тоже время использование обученных пользовательских моделей в целом расширит горизонт применения Simple для детекции, например, дефектов материала

  • Ну и конечно же другие детекторы и многое другое

Ссылки

Примеры к релизу и apk(также доступный из GooglePlay https://play.google.com/store/apps/details?id=ru.travelfood.simple_ui&pcampaignid=web_share) доступны в архиве https://disk.yandex.ru/d/49rn1ElhURSEFA

Их можно открыть в онлайн-редакторе https://seditor.ru:1555/ либо развернуть локальную версию редактора https://github.com/dvdocumentation/web_simple_editor и посмотреть на своем устройстве

Новостной телеграмм канал проекта (новости разработки, статьи, примеры): https://t.me/devsimpleui Рекомендую подписаться, там много полезного.

Форум разработчиков: https://t.me/simpledevchat

Напоминаю, что вся экосистема продуктов Simple полностью бесплатна(зарабатываю только с проектов внедрения) По всем вопросам со мной можно связаться через форму обратной связи на https://simpleui.ru/

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


  1. kimkarus
    21.01.2025 07:08

    круть