Распознавание лиц. Создаем и примеряем маски



    Пока сообщество iOS-разработчиков спорит, как писать проекты, пока пытается решить, использовать ли MVVM или VIPER, пока пытается подSOLIDить проект или добавить туда реактивную турбину, я попытаюсь оторваться от этого и рассмотреть, как работает под капотом еще одна технология с графика Hype-Driven-Development.


    В 2017 году на вершине графика хайпа — машинное обучение. И понятно почему:


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

    Машинное обучение — широкая тема, остановлюсь на распознавании лиц и попытаюсь разобраться, какие технологии были до рождества христова CoreML, и что появилось после релиза фреймворка Apple.


    Теория распознавания лиц


    Задача распознавания лиц — часть практического применения теории распознавания образов. Она состоит из двух подзадач: идентификации и классификации (тут подробно об отличиях). Идентификация личности активно используется в современных сервисах, таких как Facebook, iPhoto. Распознавание лица используется повсеместно, начиная от FaceID в iPhone X, заканчивая использованием при наведении целей в военной технике.


    Человек распознает лица других людей благодаря зоне мозга на границе затылочной и височной долей — веретеновидной извилине. Мы распознаем разных людей с 4-х месяцев. Ключевые особенности, которые выделяет мозг для идентификации, — глаза, нос, рот и брови. Также человеческий мозг восстанавливает лицо целиком даже по половине и может определить человека лишь по части лица. Все увиденные лица мозг усредняет, а потом находит отличия от этого усредненного варианта. Поэтому людям европеоидной расы кажется, что все, кто принадлежит монголоидной расе, на одно лицо. А монголоидам трудно различать европейцев. Внутреннее распознавание настроено на спектральном диапазоне лиц в голове, поэтому, если какой-то части спектра не хватает данных, лицо считается за одно и тоже.


    Задачи по распознаванию лиц решают уже более 40 лет. В них входит:


    • Поиск и распознавание нескольких лиц в видеопотоке.
    • Стойкость к изменениям лица, прически, бороды, очков, возраста и повороту лица.
    • Масштабируемость данных для идентификации человека.
    • Работа в реальном времени.

    Один из оптимальных алгоритмов для нахождения лица на картинке и его выделения — гистограмма направленных градиентов.
    Есть и иные алгоритмы. Здесь описывается подробно, как происходит поиск зоны с лицом по алгоритму Виолы-Джонса. Он менее точный и хуже работает с поворотами лица.


    Краткий экскурс в технологии и решения распознавания образов


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



    рис 1. Структура библиотеки DLIB


    DLIB


    • Плюсы:
      — Open source решение, можно участвовать в развитии и смотреть текущие тренды.
      — Написана на С++. Имеет поддержку для iOS в виде cocoapods: pod 'dlib'.
      — Можно также интегрировать в виде C++ библиотеки. Работает на Windows, Linux, MacOS. Работать можно и в swift приложениях, написав обертку на objective-c++.
    • Минусы:
      — Большой размер подключаемой библиотеки. 40 мегабайт в виде pod.
      — Высокий порог входа. Большое количество внутренних алгоритмов, под каждый из которых предстоит писать обертку на Objective-C.


    рис 2. Структура библиотеки OpenCV


    OpenCV (Open Source Computer Vision Library)


    • Плюсы:
      — Самое большое коммьюнити, регулярно участвующее в поддержке.
      — Написана на С++. Имеет поддержку для iOS в виде cocoapods: pod 'OpenCV'.
    • Минусы:
      — Высокий порог входа.
      — Большой размер подключаемой библиотеки. 77 мегабайт в виде pod, 180 мегабайт в виде C++ библиотеки.


    рис 3. Структура CoreML


    iOS Vision Framework


    • Плюсы:
      — Простая интеграция в приложение.
      — Содержит удобный конвертер, который поддерживает несколько различных моделей других фреймворков (Keras, Caffe, scikit-learn).
      — Коробочное решение с малым размером.
      — Работает на GPU.
    • Минусы:
      — Является частью CoreML, поэтому поддерживает лимитированное количество типов моделей других существующих фреймворков.
      — Нет поддержки TensorFlow, одного из самых популярных решений машинного обучения. Придется потратить много времени на самописные конвертеры.
      — Является высокоуровневой абстракцией. Вся имплементация закрыта, отсюда невозможность контроля.
      — iOS 11+.

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


    Что такое landmarks



    рис 4. Визуальное отображение структур лица


    Цель определения landmarks — нахождение точек лица. Первый шаг в алгоритме — определение локации лица на картинке. После получения локации лица ищут ключевые контуры:


    • Контур лица.
    • Левый глаз.
    • Правый глаз.
    • Левая бровь.
    • Правая бровь.
    • Левый зрачок.
    • Правый зрачок.
    • Нос.
    • Губы.

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



    рис 5. dlib 68 landmarks


    На картинке можно четко увидеть структуры лица. При этом в зависимости от выбранной библиотеки количество landmarks отличается. Разработаны решения на 4 landmarks, 16, 64, 124 и более.


    Триангуляция Делоне для построения маски


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



    рис 6. Маска, визуализирующая алгоритм триангуляции Делоне


    Триангуляция Делоне — триангуляция для множества точек S на плоскости, при которой для любого треугольника все точки из S за исключением точек, являющихся его вершинами, лежат вне окружности, описанной вокруг треугольника. Впервые описана в 1934 году советским математиком Борисом Делоне.



    рис 7. Пример триангуляции Делоне. Из каждой точки порождается окружность, проходящая через две ближайшие в метрике Евклида


    Практическая реализация алгоритма


    Реализуем алгоритм триангуляции Делоне для нашего лица в камере.


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


    public final class Triangle {
    
        public var vertex1: Vertex
        public var vertex2: Vertex
        public var vertex3: Vertex
    
        public init(vertex1: Vertex, vertex2: Vertex, vertex3: Vertex) {
            self.vertex1 = vertex1
            self.vertex2 = vertex2
            self.vertex3 = vertex3
        }
    
    }

    А vertex это wrapper для CGPoint, дополнительно содержащий номер конкретного landmark.


    public final class Vertex {
    
        public let point: CGPoint
        // Идентификатор точки. От 0 до 67. Всего 68 значений для dlib. Либо 65 для vision
        public let identifier: Int
    
        public init(point: CGPoint, id: Int) {
            self.point = point
            self.identifier = id
        }
    
    }

    Шаг 2. Перейдем к отрисовке полигонов на лице. Включаем камеру и показываем изображение с камеры на экране:


    final class ViewController: UIViewController {
        private var session: AVCaptureSession?
    
        private let faceDetection = VNDetectFaceRectanglesRequest()
        private let faceLandmarks = VNDetectFaceLandmarksRequest()
        private let faceLandmarksDetectionRequest = VNSequenceRequestHandler()
        private let faceDetectionRequest = VNSequenceRequestHandler()
    
        private lazy var previewLayer: AVCaptureVideoPreviewLayer? = {
            guard let session = self.session else {
                return nil
            }
            var previewLayer = AVCaptureVideoPreviewLayer(session: session)
            previewLayer.videoGravity = .resizeAspectFill
            return previewLayer
        }()
    
        private lazy var triangleView: TriangleView = {
            TriangleView(frame: view.bounds)
        }()
    
        private var frontCamera: AVCaptureDevice? = {
            AVCaptureDevice.default(AVCaptureDevice.DeviceType.builtInWideAngleCamera,
                                    for: AVMediaType.video, position: .front)
        }()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            sessionPrepare()
            session?.startRunning()
    
            guard let previewLayer = previewLayer else {
                return
            }
    
            view.layer.addSublayer(previewLayer)
    
            view.insertSubview(triangleView, at: Int.max)
        }
    
        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
            previewLayer?.frame = view.frame
        }
    
        private func sessionPrepare() {
            session = AVCaptureSession()
            guard let session = session, let captureDevice = frontCamera else {
                return
            }
    
            do {
                let deviceInput = try AVCaptureDeviceInput(device: captureDevice)
                session.beginConfiguration()
    
                if session.canAddInput(deviceInput) {
                    session.addInput(deviceInput)
                }
    
                let output = AVCaptureVideoDataOutput()
                output.videoSettings = [
                String(kCVPixelBufferPixelFormatTypeKey): Int(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
                ]
    
                output.alwaysDiscardsLateVideoFrames = true
    
                if session.canAddOutput(output) {
                    session.addOutput(output)
                }
    
                session.commitConfiguration()
                let queue = DispatchQueue(label: "output.queue")
                output.setSampleBufferDelegate(self, queue: queue)
                print("setup delegate")
            } catch {
                print("can't setup session")
            }
        }
    }

    Шаг 3. Далее получаем кадры с камеры



    рис 8. Пример полученного кадра с камеры


    extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
    
        func captureOutput(_ output: AVCaptureOutput,
                           didOutput sampleBuffer: CMSampleBuffer,
                           from connection: AVCaptureConnection) {
    
            guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
                return
            }
    
            guard let attachments = CMCopyDictionaryOfAttachments(kCFAllocatorDefault,
                                                                  sampleBuffer,
                                                                  kCMAttachmentMode_ShouldPropagate)
                as? [String: Any] else {
                return
            }
            let ciImage = CIImage(cvImageBuffer: pixelBuffer,
                                  options: attachments)
    
            // leftMirrored for front camera
            let ciImageWithOrientation = ciImage.oriented(forExifOrientation: Int32(UIImageOrientation.leftMirrored.rawValue))
    
            detectFace(on: ciImageWithOrientation)
        }
    
    }

    Шаг 4. Ищем лица на кадре


         fileprivate func detectFace(on image: CIImage) {
            try? faceDetectionRequest.perform([faceDetection], on: image)
            if let results = faceDetection.results as? [VNFaceObservation] {
                if !results.isEmpty {
                    faceLandmarks.inputFaceObservations = results
                    detectLandmarks(on: image)
                }
            }
        }

    Шаг 5. Ищем landmarks на лице



    рис 9. Пример найденных landmarks на лице


        private func detectLandmarks(on image: CIImage) {
            try? faceLandmarksDetectionRequest.perform([faceLandmarks], on: image)
            guard let landmarksResults = faceLandmarks.results as? [VNFaceObservation] else {
                return
            }
    
            for observation in landmarksResults {
                if let boundingBox = faceLandmarks.inputFaceObservations?.first?.boundingBox {
                    let faceBoundingBox = boundingBox.scaled(to: UIScreen.main.bounds.size)
    
                    var maparr = [Vertex]()
    
                    for (index, element) in convertPointsForFace(observation.landmarks?.allPoints,
                                                                      faceBoundingBox).enumerated() {
                        let point = CGPoint(x: (Double(UIScreen.main.bounds.size.width - element.point.x)),
                                            y: (Double(UIScreen.main.bounds.size.height - element.point.y)))
                        maparr.append(Vertex(point: point, id: index))
                    }
    
                    triangleView.recalculate(vertexes: maparr)
                }
            }
        }
    
            private func convertPointsForFace(_ landmark: VNFaceLandmarkRegion2D?,
                                          _ boundingBox: CGRect) -> [Vertex] {
            guard let points = landmark?.normalizedPoints else {
                return []
            }
            let faceLandmarkPoints = points.map { (point: CGPoint) -> Vertex in
                let pointX = point.x * boundingBox.width + boundingBox.origin.x
                let pointY = point.y * boundingBox.height + boundingBox.origin.y
    
                return Vertex(point: CGPoint(x: Double(pointX), y: Double(pointY)), id: 0)
            }
    
            return faceLandmarkPoints
        }

    Шаг 6. Далее поверх рисуем нашу маску. Берем полученные треугольники из алгоритма Делоне и рисуем в виде layers.



    рис 10. Финальный результат — простейшая маска поверх лица


    Полная реализация алгоритма триангуляции Делоне на Swift здесь.


    И пара советов по оптимизации для искушенных. Рисовать новые layers каждый раз — дорогая операция. Постоянно вычислять координаты треугольников по алгоритму Делоне тоже дорого. Поэтому берем лицо в высоком разрешении и хорошем качестве, которое смотрит в камеру, и прогоняем один раз алгоритм триангуляции Делоне на этой фотографии. Полученные треугольники сохраняем в текстовый файл, а дальше используем эту треугольники и меняем у них координаты.


    Что представляют собой маски


    MSQRD, Snapchat, VK, даже Авито — все используют маски.





    рис 11. Примеры масок в snapchat


    Реализовать простейший вариант маски легко. Берем landmarks, которые получили выше. Выбираем маску, которую хотите применить, и размещаем на ней наши landmarks. При этом существуют простейшие 2D проекции, а есть более сложные 3D маски. Для них вычисляют преобразование точек, которое переведет вершины маски на кадр. Чтобы landmarks, отвечающие за уши, отвечали за уши нашей маски. Далее просто отслеживаем новые положения landmarks лица и меняем нашу маску.


    Оригинальное лицо


    Маска в виде растянутого лица Арнольда Шварценеггера


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


    В следующей статье


    Решение задач распознавания образов, лиц, автомобильных номеров, пола, возраста становится все более востребованным. IT-компании на этом рынке вводят технологии для решения таких задач постепенно и незаметно для пользователя. Китай инвестирует 150 млрд в machine learning в течение ближайших лет, чтобы стать первым в этой области.


    В следующей статье расскажу, как идентифицировать конкретного человека по выделенному лицу и фильтровать нечеткие фотографии перед идентификацией.

    • +19
    • 6,9k
    • 7
    EPAM 64,01
    Компания
    Поделиться публикацией
    Похожие публикации
    Комментарии 7
    • +2
      Про маски: реддитор на базе опенсорсных библиотек умудрился обучить нейросеть неплохо накладывать лица знаменитостей в порно
      motherboard.vice.com/en_us/article/gydydm/gal-gadot-fake-ai-porn?utm_source=vicefbus
      • +1

        Мало найти ландмарки — нужно еще и направление лица высчитать. Вообще уважение к ребятам из msqrd — сделали давно, работает неплохо даже на слабом железе и бинарь небольшой итоговый. Все примеры dlib немного педалят на старом железе

        • 0
          Да, для грамотной работы нужно не только направление высчитывать, но и стабилизировать работу с поиском landmarks, так как камера в руках человека при всех переменах света, дерганиях и тд, является максимально хаотичным процессом. MSQRD в этом плане очень крутые.
        • 0
          Хорошая статья, познавательная. Но я пользуюсь FaceSDK (странно, что нет у вас в перечислении). Преимущество с другими в том, что редко ошибается и работет очень быстро. У меня три видеоряда, каждый по 30 кадров секунду (типа секьюрити в магазине, только своими руками) и простенький AMD — и все работает.
          • 0
            А какой именно, у многих продуктов есть FaceSDK.
          • 0
            Подскажите, есть ли в открытом доступе библиотеки на 124 landmarks?
            • 0
              Видел платное решение на 124 landmarks.
              Но, повторюсь, что увеличение количества landmarks не приведет к улучшению работы алгоритма. Есть ряд параметров, которые более важны. Такие как обработка хаотичности камеры, работа с тенями, поворотами и тд.

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

            Самое читаемое