Русский
Русский
English
Статистика
Реклама

Ios-разработка

Перевод Построители результатов в Swift описание и примеры кода

05.05.2021 12:08:12 | Автор: admin

Перевод подготовлен в рамках набора на курс "iOS Developer. Professional".

Всех желающих приглашаем на открытый демо-урок Machine Learning в iOS с помощью CoreML и CreateML: изображения, текст, звук. На занятии обсудим:

1. Основные архитектуры нейронных сетей и их оптимизированные версии под мобильные устройства;
2. Возможности CoreML 3 и 4, обучение на iOS устройстве;
3. Самостоятельное обучение классификатора изображений с помощью CreateML и использование его с Vision;
4. Использование обученных моделей для работы с текстом и звуком в iOS.


Построители результатов (result builders) в Swift позволяют получать результирующее значение из последовательности компонентов выставленных друг за другом строительных блоков. Они появились в Swift5.4 и доступны в Xcode12.5 и более поздних версиях. Ранее эти средства были известны как function builders (построители функций). Вам, вероятно, уже приходилось использовать их при создании стеков представлений в SwiftUI.

Должен признаться: поначалу я думал, что это некая узкоспециализированная возможность Swift, которую я никогда не стану применять для организации своего кода. Однако стоило мне в ней разобраться и написать небольшое решение для создания ограничений представления в UIKit, как я обнаружил, что раньше просто не понимал всю мощь построителей результатов.

Что такое построители результатов?

Построитель результата можно рассматривать как встроенный предметно-ориентированный язык (DSL), описывающий объединение неких частей в окончательный результат. В простых объявлениях представлений SwiftUI за кадром используется атрибут @ViewBuilder, который представляет собой реализацию построителя результата:

struct ContentView: View {     var body: some View {         // This is inside a result builder         VStack {             Text("Hello World!") // VStack and Text are 'build blocks'         }     } }

Все дочерние представления (в данном случае VStack, содержащий Text) будут объединены в одно представление View. Другими словами, строительные блоки View встраиваются в результат View. Это важно понять, поскольку именно так работают построители результатов.

Если рассмотреть объявление протокола View в SwiftUI, можно заметить, что переменная body определяется с использованием атрибута @ViewBuilder:

@ViewBuilder var body: Self.Body { get }

Именно так можно использовать собственный построитель результата в качестве атрибута функции, переменной или сабскрипта.

Создание собственного построителя результата

Способ определения кастомного построителя результата я покажу на примере, который использовал сам. При разработке авторазметки посредством кода я обычно реализую логику следующего вида:

var constraints: [NSLayoutConstraint] = [     // Single constraint     swiftLeeLogo.centerXAnchor.constraint(equalTo: view.centerXAnchor) ] // Boolean check if alignLogoTop {     constraints.append(swiftLeeLogo.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)) } else {     constraints.append(swiftLeeLogo.centerYAnchor.constraint(equalTo: view.centerYAnchor)) } // Unwrap an optional if let fixedLogoSize = fixedLogoSize {     constraints.append(contentsOf: [         swiftLeeLogo.widthAnchor.constraint(equalToConstant: fixedLogoSize.width),         swiftLeeLogo.heightAnchor.constraint(equalToConstant: fixedLogoSize.height)     ]) } // Add a collection of constraints constraints.append(contentsOf: label.constraintsForAnchoringTo(boundsOf: view)) // Returns an array // Activate NSLayoutConstraint.activate(constraints)

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

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

 @AutolayoutBuilder var constraints: [NSLayoutConstraint] {     swiftLeeLogo.centerXAnchor.constraint(equalTo: view.centerXAnchor) // Single constraint          if alignLogoTop {         swiftLeeLogo.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)     } else {         swiftLeeLogo.centerYAnchor.constraint(equalTo: view.centerYAnchor) // Single constraint     }          if let fixedLogoSize = fixedLogoSize {         swiftLeeLogo.widthAnchor.constraint(equalToConstant: fixedLogoSize.width)         swiftLeeLogo.heightAnchor.constraint(equalToConstant: fixedLogoSize.height)     }          label.constraintsForAnchoringTo(boundsOf: view) // Returns an array } 

Здорово, не правда ли?

Итак, рассмотрим способ создания такого решения.

Определение построителя для авторазметки

Начинаем с определения собственной структуры AutolayoutBuilder и добавляем атрибут @resultBuilder, чтобы пометить ее как построитель результата:

@resultBuilder struct AutolayoutBuilder {          // .. Handle different cases, like unwrapping and collections  } 

Чтобы объединить все строительные блоки и получить результат, нам нужно настроить обработчики для каждого случая, в частности для обработки опционалов и коллекций. Но для начала реализуем обработку случая с единственным ограничением.

Это делается с помощью следующего метода:

 @resultBuilder struct AutolayoutBuilder {          static func buildBlock(_ components: NSLayoutConstraint...) -> [NSLayoutConstraint] {         return components     }  }

Этот метод принимает на вход вариативный параметр components (тоесть параметр с переменным числом возможных значений). Это означает, что может существовать одно или несколько ограничений. Нам нужно вернуть коллекцию ограничений, то есть в этом случае мы можем напрямую вернуть входные компоненты.

Теперь мы можем определить коллекцию ограничений следующим образом:

@AutolayoutBuilder var constraints: [NSLayoutConstraint] {     // Single constraint     swiftLeeLogo.centerXAnchor.constraint(equalTo: view.centerXAnchor) } 

Обработка коллекции строительных блоков

Следующим шагом будет обработка коллекции элементов как одного элемента. В первом примере кода мы использовали удобный метод constraintsForAnchoringTo(boundsOf:), который возвращает множество ограничений в виде коллекции. Если бы мы применили его в этом случае, мы получили бы следующую ошибку:

Поначалу кастомный построитель результата не может обрабатывать коллекции компонентов.Поначалу кастомный построитель результата не может обрабатывать коллекции компонентов.

Описание ошибки отлично объясняет происходящее:

Cannot pass array of type [NSLayoutConstraint] as variadic arguments of type NSLayoutConstraint Невозможно передать массив типа [NSLayoutConstraint] как вариативные аргументы типа NSLayoutConstraint

Как ни странно, в Swift нельзя передавать массив в качестве вариативных параметров. Вместо этого нужно определить собственный метод для обработки коллекции в качестве входных компонентов. На первый взгляд может показаться, что нам нужен следующий доступный метод:

Список доступных методов в определении кастомного построителя результата.Список доступных методов в определении кастомного построителя результата.

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

Можно решить эту проблему, определив новый протокол, который реализуется как с использованием одного NSLayoutConstraint, так и с использованием коллекции ограничений:

 protocol LayoutGroup {     var constraints: [NSLayoutConstraint] { get } } extension NSLayoutConstraint: LayoutGroup {     var constraints: [NSLayoutConstraint] { [self] } } extension Array: LayoutGroup where Element == NSLayoutConstraint {     var constraints: [NSLayoutConstraint] { self } } 

Этот протокол позволит нам преобразовывать как отдельные ограничения, так и коллекцию ограничений в массив ограничений. Другими словами, мы можем объединить оба типа в один [NSLayoutConstraint].

Теперь мы можем переписать наш построитель результата так, чтобы он принимал наш протокол LayoutGroup:

 @resultBuilder struct AutolayoutBuilder {          static func buildBlock(_ components: LayoutGroup...) -> [NSLayoutConstraint] {         return components.flatMap { $0.constraints }     } } 

Для получения единой коллекции ограничений здесь используется метод flatMap. Если вы не знаете, для чего нужен метод flatMap или почему мы использовали его вместо compactMap, почитайте мою статью Методы compactMap и flatMap:в чем разница?

Наконец, мы можем обновить наше решение, чтобы задействовать новый обработчик коллекции строительных блоков:

@AutolayoutBuilder var constraints: [NSLayoutConstraint] {     // Single constraint     swiftLeeLogo.centerXAnchor.constraint(equalTo: view.centerXAnchor)          label.constraintsForAnchoringTo(boundsOf: view) // Returns an array } 

Разворачивание опционалов

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

Добавим метод buildOptional(..) к нашему построителю результата:

 @resultBuilder struct AutolayoutBuilder {          static func buildBlock(_ components: LayoutGroup...) -> [NSLayoutConstraint] {         return components.flatMap { $0.constraints }     }          static func buildOptional(_ component: [LayoutGroup]?) -> [NSLayoutConstraint] {         return component?.flatMap { $0.constraints } ?? []     } } 

Метод пытается преобразовать результат в коллекцию ограничений или возвращает пустую коллекцию, если данного значения не существует.

Теперь мы можем развернуть опционал в нашем определении строительных блоков:

@AutolayoutBuilder var constraints: [NSLayoutConstraint] {     // Single constraint     swiftLeeLogo.centerXAnchor.constraint(equalTo: view.centerXAnchor)          label.constraintsForAnchoringTo(boundsOf: view) // Returns an array          // Unwrapping an optional     if let fixedLogoSize = fixedLogoSize {         swiftLeeLogo.widthAnchor.constraint(equalToConstant: fixedLogoSize.width)         swiftLeeLogo.heightAnchor.constraint(equalToConstant: fixedLogoSize.height)     } } 

Обработка условных операторов

Еще один распространенный случай условные операторы. В зависимости от логического значения может потребоваться добавить то или иное ограничение. Этот обработчик может обрабатывать первый или второй компонент в проверке условия:

 @AutolayoutBuilder var constraints: [NSLayoutConstraint] {     // Single constraint     swiftLeeLogo.centerXAnchor.constraint(equalTo: view.centerXAnchor)          label.constraintsForAnchoringTo(boundsOf: view) // Returns an array          // Unwrapping an optional     if let fixedLogoSize = fixedLogoSize {         swiftLeeLogo.widthAnchor.constraint(equalToConstant: fixedLogoSize.width)         swiftLeeLogo.heightAnchor.constraint(equalToConstant: fixedLogoSize.height)     }          // Conditional check     if alignLogoTop {         // Handle either the first component:         swiftLeeLogo.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)     } else {         // Or the second component:         swiftLeeLogo.centerYAnchor.constraint(equalTo: view.centerYAnchor)     } } 

В наш построитель результата надо добавить еще пару обработчиков строительных блоков:

 @resultBuilder struct AutolayoutBuilder {          static func buildBlock(_ components: LayoutGroup...) -> [NSLayoutConstraint] {         return components.flatMap { $0.constraints }     }          static func buildOptional(_ component: [LayoutGroup]?) -> [NSLayoutConstraint] {         return component?.flatMap { $0.constraints } ?? []     }          static func buildEither(first component: [LayoutGroup]) -> [NSLayoutConstraint] {         return component.flatMap { $0.constraints }     }      static func buildEither(second component: [LayoutGroup]) -> [NSLayoutConstraint] {         return component.flatMap { $0.constraints }     } } 

В обоих обработчиках buildEither для получения ограничений и их возвращения в виде плоской структуры используется все тот же протокол LayoutGroup.

Это были последние два обработчика, необходимые для работы нашего примера. Ура!

Однако мы еще не закончили. Мы можем немного усовершенствовать этот код, используя построители результатов внутри функций.

Использование построителей результатов в качестве параметров функций

Отличный способ использовать построитель результата определить его как параметр функции. Так мы действительно получим пользу от нашего кастомного AutolayoutBuilder.

Например, можно добавить такое расширение к NSLayoutConstraint, чтобы немного упростить активацию ограничений:

extension NSLayoutConstraint {     /// Activate the layouts defined in the result builder parameter `constraints`.     static func activate(@AutolayoutBuilder constraints: () -> [NSLayoutConstraint]) {         activate(constraints())     } 

Применяться расширение будет вот так:

NSLayoutConstraint.activate {     // Single constraint     swiftLeeLogo.centerXAnchor.constraint(equalTo: view.centerXAnchor)          label.constraintsForAnchoringTo(boundsOf: view) // Returns an array          // Unwrapping an optional     if let fixedLogoSize = fixedLogoSize {         swiftLeeLogo.widthAnchor.constraint(equalToConstant: fixedLogoSize.width)         swiftLeeLogo.heightAnchor.constraint(equalToConstant: fixedLogoSize.height)     }          // Conditional check     if alignLogoTop {         // Handle either the first component:         swiftLeeLogo.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)     } else {         // Or the second component:         swiftLeeLogo.centerYAnchor.constraint(equalTo: view.centerYAnchor)     } } 

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

 protocol SubviewContaining { } extension UIView: SubviewContaining { } extension SubviewContaining where Self == UIView {          /// Add a child subview and directly activate the given constraints.     func addSubview<View: UIView>(_ view: View, @AutolayoutBuilder constraints: (Self, View) -> [NSLayoutConstraint]) {         addSubview(view)         NSLayoutConstraint.activate(constraints(self, view))     } } 

Это можно использовать следующим образом:

 let containerView = UIView() containerView.addSubview(label) { containerView, label in          if label.numberOfLines == 1 {         // Conditional constraints     }          // Or just use an array:     label.constraintsForAnchoringTo(boundsOf: containerView)      } 

Поскольку мы используем дженерики, мы можем выполнять проверку условий на основе входного типа UIView. В этом случае можно добавить различные ограничения, если метка label будет содержать только одну строку текста.

Как разработать собственное решение с построителем результата?

Вы, наверняка, думаете: как же определить, будет ли построитель результата полезен в том или ином фрагменте кода?

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

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

Наконец, я хотел бы сослаться на репозиторий с примерами построителей функций (которые теперь называются построителями результатов).

Заключение

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


Узнать подробнее о курсе "iOS Developer. Professional"

Смотреть вебинар Machine Learning в iOS с помощью CoreML и CreateML: изображения, текст, звук

Подробнее..

21 и 22 января два бесплатных онлайн-митапа (QA и iOS)

14.01.2021 16:09:15 | Автор: admin

Привет! Новый год новые митапы.

Уже через неделю мы проведём два первых в этом году митапа, первый из которых будет полезен тестировщикам, а второй iOS-разработчикам. Спикеры будут из Альфа-Банка, а вот участники круглого стола по теме из Сбера, Тинькофф и Райффайзенбанка.

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

Программа под катом

21 января, 19.00 МСК QAчественное общение

В программе 3 доклада от Альфа-Банка и викторина с призами. Мы постараемся сделать эти пару часов максимально полезными для всех собравшихся.

Чтобы принять участие в митапе, обязательно зарегистрируйтесь. Мы отправим вам напоминание и ссылку на трансляцию.

19:05-19:40, Дмитрий Гадеев, Kubernetes. Жизнь ДО и ПОСЛЕ

Руководитель направления сопровождения инфраструктурных сервисов, Альфа-Банк

Дмитрий расскажет о тех вопросах и нюансах, про которые постоянно забывают, внедряя у себя Kubernetes, и к чему пришла команда alfabank.ru за два года его использования в тестовых средах и на бою.

19:40-20:10, Ксения Коломиец, Сайт-конструктор. Тестирование без боли

Старший специалист по тестированию, Альфа-Банк

Любую техническую проблему можно преодолеть, имея достаточно времени и денег. Но вам никогда не будет хватать либо времени, либо денег. Деньги у нас есть, а вот времени на релиз - всего неделя. Это история о том, как мы в сжатые сроки стараемся каждый релиз повышать качество на проекте сайта.

20:10-20:40, Александр Долинский, Тестирование API в большой команде

Руководитель группы тестирования, Альфа-Банк

Пройдем по истории развития тестирования мобильного банка. Соберем некоторые грабли большого проекта.

20:40-21:00, Викторина в Kahoot с розыгрышем призов

Страница регистрации

22 января, 19.00 МСК Mobile Talks

19:05-19:40, Василий Пономарев, Техническая сторона UI-компонентов

iOS-разработчик, Альфа-Банк

Василий расскажет о реализации UI-компонентов, принятых на вооружение архитектурных решениях. А также покажет пример из практики и разберет преимущества и недостатки данного подхода.

19:40-20:15, Евгений Онуфрейчик, Новый разработчик. От старта до выхода на орбиту

iOS-разработчик, Альфа-Банк

Женя расскажет о процессе погружения нового разработчика в проект мобильного приложения Альфа-Банка, о сложностях адаптации и как мы с ними справляемся. Объяснит, как выглядит процесс со стороны новичка что он ощущает и как понимает происходящее. А также расскажет, какова в этом роль и зоны ответственности наставника.

20:15-20:35, Викторина в Kahoot с розыгрышем призов

20:35-21:35, Круглый стол Качество vs скорость разработки как найти баланс?

  • Сергей Нанаев, Head of iOS, Альфа-Банк

  • Роман Голофаев, Mobile Community Lead, Райффайзенбанк

  • Александр Поломодов, Руководитель управления разработки цифровых экосистем, Тинькофф

  • Иван Бубнов, Руководитель направления мобильной разработки, Сбер

Страница регистрации.

Подробнее..

Распознание блоков текста в IOS-приложении с помощью Vision

18.02.2021 14:23:54 | Автор: admin

Работая над приложением, связанным с финансовыми операциями, возникла необходимость распознать и выделить суммы на чеках. Начиная с 13-ой версии в IOS-разработке появился нативный фреймворк Vision, который позволяет распознавать различные объекты на изображениях, без задействования сторонних сервисов.
В данной статье представлен личный опыт разработки приложения, использующего Vision.

Что такое Vision

Из документации Apple: "Vision применяет алгоритмы "компьютерного зрения" для выполнения множества задач с входными изображениями и видео. Фреймворк Vision выполняет распознание лиц, обнаружение текста, распознавание штрих-кодов, регистрацию изображений. Vision также позволяет использовать пользовательские модели CoreML для таких задач, как классификация или обнаружение объектов."
Анализируя документацию Apple, можно предположить, что Vision - это один из этапов подготовки таких продуктов как Apple glasses или шлем смешанной реальности. Забегая вперед, следует подчеркнуть, что данный фреймворк потребляет изрядное количество ресурсов. Обработка статичного изображения может занимать десятки секунд, следовательно, работа с видео в реальном времени будет предельно ресурсоемким процессом, над оптимизацией которого инженерам Apple еще предстоит поработать.
В рамках поставленной задачи, необходимо было решить следующую проблему: распознание блоков текста с помощью Vision.

Разработка

Проект построен на UIKit, который в данной статье детально рассматриваться не будет. Основное внимание уделяется блокам кода, связанным с фреймворком Vision. Приведенные листинги снабжены комментариями, позволяющими разработчикам детальнее понять принцип работы с фреймворком.
В MainViewController, который будет взаимодействовать с фреймворком Vision, нужно объявить две переменные:

//Recognition queuelet textRecognitionWorkQueue = DispatchQueue(label: "TextRecognitionQueue", qos: .userInitiated, attributes: [], autoreleaseFrequency: .workItem)//Request for text recognitionvar textRecognitionRequest: VNRecognizeTextRequest?
  1. Очередь для задач Vision не вызывает никаких затруднений у разработчиков. Именно в ней будут выполняться все задачи фреймворка.

  2. Объявляется переменная типа VNRecognizeTextRequet. Инициализируется объект из ViewDidLoad (или из init), так как он должен быть активен на протяжении всей жизни ViewController. Этот объект отвечает за работу с Vision, поэтому необходимо разобрать его инициализацию подробнее:

//Set textRecognitionRequest from ViewDidLoadfunc setTextRequest() {    textRecognitionRequest = VNRecognizeTextRequest { request, error in        guard let observations = request.results as? [VNRecognizedTextObservation] else {            return        }        var detectedText = ""        self.textBlocks.removeAll()                    for observation in observations {            guard let topCandidate = observation.topCandidates(1).first else { continue }            detectedText += "\(topCandidate.string)\n"                        //Text block specific for this project            if let recognizedBlock = self.getRecognizedDoubleBlock(topCandidate: topCandidate.string, observationBox: observation.boundingBox) {                self.textBlocks.append(recognizedBlock)            }        }                    DispatchQueue.main.async {            self.textView.text = detectedText            self.removeLoader()            self.drawRecognizedBlocks()        }    }            //Individual recognition request settings    textRecognitionRequest!.minimumTextHeight = 0.011 // Lower = better quality    textRecognitionRequest!.recognitionLevel = .accurate}

Настройки объекта textRecognitionRequest. Описание всех доступных настроек можно найти в документации. Наиболее важным является параметр minimumTextHeight. Именно этот параметр отвечает за сочетание быстродействия и точности распознания текста. Для каждого проекта необходимо найти индивидуальное значение данного параметра, оно зависит от того, какие данные будет обрабатывать приложение.
Так как основной поставленной задачей являлось считывание текста с квитанций, для вычисления значения параметра minimumTextHeight в приложение были добавлены различные типы квитанций в различном состоянии (в том числе и основательно помятые). В результате тестирования было определено значение равное 0.011. В случае распознания текста с квитанций, это значение лучшим образом сочетает в себе быстродействие и точность. Однако нужно отметить, что текст с одного изображения распознается в среднем за пять секунд. Подобной скорости недостаточно для обработки информации в реальном времени и ее следует значительно оптимизировать инженерам Apple.
На основе представленного кода можно сделать вывод, что после операции распознания, объект типа VNRecognizeTextRequet получает блоки текста. Именно с ними и ведется дальнейшая работа, в зависимости от функций приложения. В рассматриваемом примере, каждый распознанный фрагмент текста был внесен в текстовое поле. Так как особенностью задействованного приложения является выделение суммы на квитанции, следовательно, сохранялись только блоки текста, которые можно преобразовать в тип Double. Помимо распознанного текстового значения сохраняются и координаты блока текста на изображении.
Представленный ниже метод отвечает за запуск работы запроса на распознание:

//Call text recognition request handlerfunc recognizeImage(cgImage: CGImage) {    textRecognitionWorkQueue.async {        let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:])        do {            try requestHandler.perform([self.textRecognitionRequest!])        } catch {            DispatchQueue.main.async {                self.removeLoader()                print(error)            }        }    }}

В метод передается объект CGImage, в котором необходимо распознать текст. Вся работа по распознанию ведется в созданной для этого очереди. Создается объект VNImageRequestHandler, в который передается распознаваемый объект CGImage. В блоке do/try/catch запускается работа инициализированного объекта типа VNRecognizeTextRequet.
Описанные выше функции отвечают за распознание текста в приложении. Однако стоит еще остановится на методах, связанных с выделением нужных блоков текста.

func drawRecognizedBlocks() {    guard let image = invoiceImage?.image else  { return }        //transform from documentation    let imageTransform = CGAffineTransform.identity.scaledBy(x: 1, y: -1).translatedBy(x: 0, y: -image.size.height).scaledBy(x: image.size.width, y: image.size.height)            //drawing rects on cgimage    UIGraphicsBeginImageContextWithOptions(image.size, false, 1.0)    let context = UIGraphicsGetCurrentContext()!    image.draw(in: CGRect(origin: .zero, size: image.size))    context.setStrokeColor(CGColor(srgbRed: 1, green: 0, blue: 0, alpha: 1))    context.setLineWidth(4)        for index in 0 ..< textBlocks.count {        let optimizedRect = textBlocks[index].recognizedRect.applying(imageTransform)        context.addRect(optimizedRect)        textBlocks[index].imageRect = optimizedRect    }    context.strokePath()            let result = UIGraphicsGetImageFromCurrentImageContext()    UIGraphicsEndImageContext()    invoiceImage?.image = result}

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

struct RecognizedTextBlock {    let doubleValue: Double    let recognizedRect: CGRect    var imageRect: CGRect = .zero}

При распознании блоков текста фреймворк Vision вычисляет ряд важных параметров в объекте VNRecognizedTextObservation. Для нужд рассматриваемого проекта необходимо было получить только значение типа Double и его координаты на изображении, сохраняемые в константе recognizedRect.
Для выделения блока текста на изображении, следует применить трансформацию к координатам из константы recognizedRect. Полученные координаты так же сохраняются в объекте RecognizedTextBlock в переменной imageRect, необходимой для обработки нажатий на выделенные блоки текста.
После сохранения точных координат выделяемых блоков на изображении, обработку нажатий на выделенные области можно осуществить несколькими способами:

  • Добавить необходимое количество невидимых кнопок на изображение, при помощи трансформации сохраненного объекта imageRect;

  • При каждом нажатии на изображение проверять массив блоков текста и искать совпадение координат нажатия с сохраненным объектом imageRect и др.

Чтобы не перегружать ViewController дополнительными элементами, был использован второй способ.

//UIImageView tap listener@objc func onImageViewTap(sender: UITapGestureRecognizer) {    guard let invoiceImage = invoiceImage, let image = invoiceImage.image else {        return    }            //get tap coordinates on image    let tapX = sender.location(in: invoiceImage).x    let tapY = sender.location(in: invoiceImage).y    let xRatio = image.size.width / invoiceImage.bounds.width    let yRatio = image.size.height / invoiceImage.bounds.height    let imageXPoint = tapX * xRatio    let imageYPoint = tapY * yRatio    //detecting if one of text blocks tapped    for block in textBlocks {        if block.imageRect.contains(CGPoint(x: imageXPoint, y: imageYPoint)) {            showTapAlert(doubleValue: block.doubleValue)            break        }    }}

Использование представленного метода позволяет вычислить координаты нажатия на объект ImageView с сохранением его пропорций и поиска полученных координат в массиве сохранных распознанных блоков текста.

Выводы

Представленная статья позволяет разработчикам IOS-приложений ознакомиться с фреймворком Vision, прежде всего с его функцией распознания текста. Тестовое приложение, полученное в результате работы с распознанием текста в Vision, является ключом к пониманию работы с такими возможностями фреймворка как распознание лиц, текста, штрих-кодов и др.
Cкриншоты полученного приложения для распознавания цифровых значений на различных изображениях представлены ниже:

Приложение распознающее блоки текста с помощью VisionПриложение распознающее блоки текста с помощью Vision

Для ознакомления проект можно скачать из репозитория.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru