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

в 9:01, , рубрики: iOS, ios development, machine learning, swift, xcode, Алгоритмы, Блог компании EPAM, Программирование, разработка мобильных приложений, разработка под iOS

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

DLIB

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

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

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

OpenCV (Open Source Computer Vision Library)

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

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

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

iOS Vision Framework

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

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

Что такое landmarks

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

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

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

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

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

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

рис 5. dlib 68 landmarks

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

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

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

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

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

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

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

рис 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. Далее получаем кадры с камеры

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

рис 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 на лице

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

рис 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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Автор: Николай Ашанин

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js