[Перевод] Создание GIF-анимации с помощью OpenCV

g1sxhdlqfra2bohbg5d7yjliv6g.gif

Из этого туториала вы узнаете, как создавать анимированные GIF-файлы с помощью OpenCV, Python и ImageMagick. Затем объедините эти методы, чтобы создать генератор мемов с OpenCV!

Нам всем нужно время от времени посмеяться. И, возможно, лучший способ найти лулзы — это мемы. Некоторые из моих любимых:

  • Лягушка Кермит: «Но это не мое дело»
  • Сварливый кот
  • Эпик фейл
  • Хороший парень Грег


Но лично для меня ни один из этих мемов не сравнится с мемом «Deal With It» («Смирись с этим» или «Разбирайся сам»), пример которого приведён в начале статьи.
Обычно он используется в следующих случаях:

  1. В качестве ответа или возражения тому, кто не одобряет нечто, что вы сделали/сказали («Смирись с этим»)
  2. Надевая очки, словно вы уходите и оставляете человека наедине с проблемой («Разбирайся сам»)


Несколько лет назад я читал весёлую статью в блоге автора, которого уже не могу вспомнить, как генерировать такие мемы с помощью компьютерного зрения. На прошлой неделе я так нигде и не смог найти это руководство, поэтому как блоггер, эксперт по компьютерному зрению и знаток мемов, я решил сам написать туториал! (Кстати, если вы случайно знаете оригинальный источник, пожалуйста, дайте мне знать, чтобы я мог выразить благодарность автору. UPD: Только что нашёл оригинальную статью из блога Кирка Кайзера MakeArtWithPython).

Разработка генератора мемов на OpenCV научит нас ряду ценных практических навыков, в том числе:

  1. Обнаружение лиц с помощью техник глубокого обучения
  2. Применение библиотеки dlib для обнаружения ориентиров лица и извлечения областей глаз
  3. Как вычислить угол поворота между глазами на основе полученной информации
  4. И, наконец, как генерировать анимированные GIF-файлы с помощью OpenCV (с небольшой помощью ImageMagick)


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

Затем рассмотрим структуру проекта/каталога для нашего генератора гифок на OpenCV.

Как только поймём структуру проекта, мы рассмотрим: 1) наш конфигурационный файл; 2) скрипт Python, ответственный за создание GIF с OpenCV.

Наконец, оценим результаты работы программы на популярном меме «Deal With It».

Предварительные требования и зависимости


ad6c3e193373bc04c877e60070d73071.png
Рис. 1. Для создания гифок будем использовать OpenCV, dlib и ImageMagick

OpenCV и dlib


OpenCV нужен для определения лиц в кадре и базовой обработки изображений. Следуйте одному из моих руководств по установке OpenCV, если в системе не установлен OpenCV.

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

ImageMagick


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

Хотите одной командой преобразовать PNG/JPG в PDF? Без проблем.

Есть несколько изображений, из которых нужно сделать многостраничный PDF? Запросто.

Нужно рисовать многоугольники, линии и другие фигуры? И это возможно.

Как насчёт пакетной цветокорректировки или изменения размеров всех картинок одной командой? Чтобы это сделать, не нужно писать несколько строк на Python для OpenCV.

ImageMagick также генерирует гифки из любых изображений.

Для установки ImageMagick на Ubuntu (или Raspbian) просто используйте apt:

Создание гифки с OpenCVShell

$ sudo apt-get install imagemagick


На macOS можно задействовать HomeBrew:

$ brew install imagemagick


imutils


В большинстве статей, курсов и книг я использую свой удобный пакет функций обработки изображений imutils. Он устанавливается в системе или виртуальной среде с помощью pip:

$ pip install imutils


Структура проекта


e42281af07fb94c9ae60a526098f662d.png
Рис. 2. Структура проекта включает два каталога, конфигурационный файл и скрипт Python

В нашем проекте два каталога:

  • images/: примеры входных изображений, для которых мы хотим сделать анимированный GIF. Я нашёл несколько изображений с собой, но не стесняйтесь добавлять свои собственные.
  • assets/: эта папка содержит наш детектор лица, детектор ориентира лица и все изображения + связанные маски. С этими активами мы будем накладывать очки и текст на исходные изображения из первой папки.


Из-за большого количества настраиваемых параметров я решил создать конфигурационный файл JSON, который: 1) облегчит редактирование параметров; 2) потребует меньше аргументов командной строки. Все параметры конфигурации, необходимые для этого проекта, содержатся в config.json.

Рассмотрим содержимое config.json и create_gif.py.

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

Генерация GIF с OpenCV


Итак, продолжим и начнем создавать наше генератор OpenCV GIF!

Содержание конфигурационного файла JSON


Начнём с файла конфигурации JSON, а затем перейдём к скрипту Python.

Откройте новый файл config.json и вставьте следующие пары ключ/значение:

Создание гифки с OpenCVPython

{
        "face_detector_prototxt": "assets/deploy.prototxt",
        "face_detector_weights": "assets/res10_300x300_ssd_iter_140000.caffemodel",
        "landmark_predictor": "assets/shape_predictor_68_face_landmarks.dat",


Это файлы модели детектора лица OpenCV на глубоком обучении.

Последняя строка — путь к предиктору лицевых ориентиров dlib.

И теперь у нас есть некоторые пути к файлам изображений:

"sunglasses": "assets/sunglasses.png",
        "sunglasses_mask": "assets/sunglasses_mask.png",
        "deal_with_it": "assets/deal_with_it.png",
        "deal_with_it_mask": "assets/deal_with_it_mask.png",


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

Во первых, причудливые солнечные очки и маска:

c18c4c6471e203aedea66a80c9080192.png


Рис. 3. Вам не нравятся очки с пикселями? Просто смиритесь с этим

58b91ad9c2e66238b3f3ac22fe1ee439.png


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

А теперь наш текст «DEAL WITH IT» и маска:

ec34e4923e2d05a5b33c065de8f5af66.png


Рис. 5. Ненавидишь Helvetica Neue Condensed? Смирись с этим

c6cf708198bda68d849029b3ded02265.png


Рис. 6: Эта маска позволяет сделать границу вокруг текста. Ой, а может вам не нужны не хотите границы? Ну, смиритесь с этим

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

Теперь установим некоторые параметры для генератора мемов:

      "min_confidence": 0.5,
        "steps": 20,
        "delay": 5,
        "final_delay": 250,
        "loop": 0,
        "temp_dir": "temp"
}


Вот определения для каждого из параметров:

  • min_confidence: минимальная требуемая вероятность обнаружения лица.
  • steps: число кадров в итоговой анимации. Каждый «шаг» перемещает солнцезащитные очки от верхней границы вниз к цели (т. е. к глазам).
  • delay: задержка между кадрами в сотых долях секунды.
  • final_delay: задержка последнего кадра в сотых долях секунды (полезно в этом контексте, так как мы хотим, чтобы текст отображался дольше, чем остальные кадры).
  • loop: нулевое значение указывает, что GIF повторяется вечно, в противном случае укажите положительное целое число для количества повторений анимации.
  • temp_dir: временный каталог, в котором хранится каждый из кадров будет перед созданием окончательного GIF.


Мемы, GIF и OpenCV


Мы создали файл конфигурации JSON, теперь перейдём к реальному коду.

Откройте новый файл, назовите его create_gif.py и вставьте следующий код:

# импорт необходимых пакетов
from imutils import face_utils
from imutils import paths
import numpy as np
import argparse
import imutils
import shutil
import json
import dlib
import cv2
import sys
import os


Здесь мы импортируем необходимые пакеты. В частности, будем использовать imutils, dlib и OpenCV. Для установки этих зависимостей см. раздел «Необходимые компоненты и зависимости» выше.

Теперь в скрипте есть необходимые пакеты, так что определим функцию overlay_image:

def overlay_image(bg, fg, fgMask, coords):
        # определить размер переднего плана (ширина, высота) и
        # координаты его размещения
        (sH, sW) = fg.shape[:2]
        (x, y) = coords
 
        # наложение должно быть точно такой ширины и высоты как 
        # исходная картинка, но полностью пустым, *кроме* переднего
        # плана, который мы добавляем
        overlay = np.zeros(bg.shape, dtype="uint8")
        overlay[y:y + sH, x:x + sW] = fg
 
        # альфа-канал контролирует, *координаты* и *степень*
        # прозрачности, его размеры такие же, как у исходного
        # изображения, но он содержит только маску наложения
        alpha = np.zeros(bg.shape[:2], dtype="uint8")
        alpha[y:y + sH, x:x + sW] = fgMask
        alpha = np.dstack([alpha] * 3)
 
        # выполняем альфа-смешивание для переднего плана,
        # фона и альфа-канала
        output = alpha_blend(overlay, bg, alpha)
 
        # возвращаем результат
        return output


Функция overlay_image накладывает передний план (fg) на верхнюю часть фонового изображения (bg) по координатам coords (координаты (x, y)), реализуя альфа-прозрачность по маске переднего плана fgMask.

Чтобы ознакомиться с основами OpenCV, такими как работа с масками, не забудьте прочитать это руководство.

Для завершения процесса наложения выполняем альфа-смешивание:

def alpha_blend(fg, bg, alpha):
        # преобразуем фон, передний план и альфа-канал
        # в числа с плавающей запятой в диапазоне [0, 1]
        fg = fg.astype("float")
        bg = bg.astype("float")
        alpha = alpha.astype("float") / 255
 
        # выполняем альфа-смешивание
        fg = cv2.multiply(alpha, fg)
        bg = cv2.multiply(1 - alpha, bg)
 
        # добавляем передний план и фон, получая конечный результат
        output = cv2.add(fg, bg)
        
        # возвращаем результат
        return output.astype("uint8")


Эта реализация альфа-смешивания также приводится в блоге LearnOpenCV.

По сути, мы преобразуем передний план, фона и альфа-канал в числа с плавающей запятой в диапазоне [0, 1]. Затем выполняем альфа-смешивание, добавляем передний план и фон, чтобы получить результат, который возвращаем вызывающей функции.

Создадим также вспомогательную функцию, которая позволит генерировать GIF из набора путей изображения с помощью ImageMagick и команды convert:

def create_gif(inputPath, outputPath, delay, finalDelay, loop):
        # получить все пути из папки исходных изображений
        imagePaths = sorted(list(paths.list_images(inputPath)))
        
        # удалить последний путь в списке
        lastPath = imagePaths[-1]
        imagePaths = imagePaths[:-1]
 
        # сконструировать команду imagemagick 'convert' для
        # генерации GIF с более длительной задержкой для
        # последнего кадра (если необходимо)
        cmd = "convert -delay {} {} -delay {} {} -loop {} {}".format(
                delay, " ".join(imagePaths), finalDelay, lastPath, loop,
                outputPath)
        os.system(cmd)


Функция create_gif берёт набор изображений и собирает их в GIF-анимацию с заданной задержкой между кадрами и циклами. Всё это обрабатывает ImageMagick — мы просто оборачиваем команду convert в функцию, которая динамически обрабатывает различные параметры.

Чтобы просмотреть доступные аргументы convert, обратитесь к документации. Там увидите, как много функций у этой команды!

Конкретно в данной функции мы:

  • Берём imagePaths.
  • Выбираем путь последнего изображения, у которого будет отдельная задержка.
  • Переназначаем imagePaths, чтобы исключить последний путь.
  • Собираем команду с аргументами командной строки, а затем поручаем операционной системе выполнить convert для создания GIF-анимации.


Назначим скрипту собственные аргументы командной строки:

# сооружаем парсер и разбираем аргументы
ap = argparse.ArgumentParser()
ap.add_argument("-c", "--config", required=True,
        help="path to configuration file")
ap.add_argument("-i", "--image", required=True,
        help="path to input image")
ap.add_argument("-o", "--output", required=True,
        help="path to output GIF")
args = vars(ap.parse_args())


У нас три аргумента командной строки, которые обрабатываются во время выполнения:

  • --config: путь к файлу конфигурации JSON. Мы рассмотрели файл конфигурации в предыдущем разделе.
  • --image: путь к входному изображению, на фоне которого создаётся анимация (т.е. обнаружение лица, добавление солнцезащитных очков, а затем текста).
  • --output: путь к итоговому GIF.


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

Загрузим файл конфигурации, а также очки и соответствующую маску:

# загружаем конфигурационный файл JSON,
# очки и соответствующую маску
config = json.loads(open(args["config"]).read())
sg = cv2.imread(config["sunglasses"])
sgMask = cv2.imread(config["sunglasses_mask"])
 
# удаляем временную папку (если она существует), а затем
# создаём новую, пустую папку, где будем сохранять каждый
# отдельный кадр GIF-анимации
shutil.rmtree(config["temp_dir"], ignore_errors=True)
os.makedirs(config["temp_dir"])


Здесь мы загружаем файл конфигурации (который в будущем может быть доступен как словарь Python). Затем загружаем солнцезащитные очки и маску.

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

Теперь загрузим в память детектор лиц глубокого обучения OpenCV:

# load our OpenCV face detector and dlib facial landmark predictor
print("[INFO] loading models...")
detector = cv2.dnn.readNetFromCaffe(config["face_detector_prototxt"],
        config["face_detector_weights"])
predictor = dlib.shape_predictor(config["landmark_predictor"])


Для этого вызываем cv2.dnn.readNetFromCaffe. Модуль dnn доступен только в OpenCV 3.3 или более поздней версии. Детектор лица позволит обнаружить присутствие лиц на изображении:

a91d3a0cde3dcb3cac9799c823e47c8c.jpg
Рис. 7. Работа детектора лиц спользованием OpenCV DNN

Затем загружаем предиктор ориентиров лица dlib. Он позволит локализовать отдельные структуры: глаза, брови, нос, рот и линию подбородка:

0568168909b7f01d2f70f6e58faa9ed9.jpg
Рис. 8. На моём лице наложены ориентиры, обнаруженные dlib

Позже в этом скрипте извлечём только области глаз.

Двигаемся дальше, давайте обнаружим лицо:

# загружаем исходное изображение и создаём блоб
image = cv2.imread(args["image"])
(H, W) = image.shape[:2]
blob = cv2.dnn.blobFromImage(cv2.resize(image, (300, 300)), 1.0,
        (300, 300), (104.0, 177.0, 123.0))
 
# передаём блоб в нейросеть и получаем результаты
print("[INFO] computing object detections...")
detector.setInput(blob)
detections = detector.forward()
 
# для наложения очков нужно только одно лицо, поэтому
# определяем лицо, для которого выдаётся максимальная вероятность
i = np.argmax(detections[0, 0, :, 2])
confidence = detections[0, 0, i, 2]
 
# отфильтровываем слабые результаты
if confidence < config["min_confidence"]:
        print("[INFO] no reliable faces found")
        sys.exit(0)


В этом блоке мы делаем следующее:

  • Загружаем исходный image.
  • Конструируем blob для отправки в детектор лиц нейросети. В этой статье описано, как работает blobFromImage из OpenCV.
  • Выполняем процедуру обнаружения лиц.
  • Находим лицо с наибольшим значением вероятности и сравниваем его с минимально допустимым порогом вероятности. Если критерии не выполняются, просто выходим из скрипта. В противном случае продолжаем.


Теперь извлечём лицо и вычислим ориентиры:

# вычисляем координаты (x, y) ограничительной
# рамки на лице
box = detections[0, 0, i, 3:7] * np.array([W, H, W, H])
(startX, startY, endX, endY) = box.astype("int")
 
# конструируем прямоугольный объект dlib из координат ограничительной
# рамки и определяем ориентиры внутри него
rect = dlib.rectangle(int(startX), int(startY), int(endX), int(endY))
shape = predictor(image, rect)
shape = face_utils.shape_to_np(shape)
 
# берём индексы ориентиров для левого и правого глаз, затем
# вычисляем координаты каждого глаза
(lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"]
(rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]
leftEyePts = shape[lStart:lEnd]
rightEyePts = shape[rStart:rEnd]


Для извлечения лица и нахождения лицевых ориентиров мы делаем следующее:

  • Извлекаем координаты ограничительной рамки вокруг лица.
  • Создаём объект rectangle в dlib и применяем локализацию ориентиров лица.
  • Извлекаем (x, y)-координаты leftEyePts и rightEyePts, соответственно.


По заданным координатам глаз можно рассчитать, где и как размещать солнцезащитные очки:

# вычисляем центр массы для каждого глаза
leftEyeCenter = leftEyePts.mean(axis=0).astype("int")
rightEyeCenter = rightEyePts.mean(axis=0).astype("int")
 
# вычисляем угол между центроидами глаз
dY = rightEyeCenter[1] - leftEyeCenter[1]
dX = rightEyeCenter[0] - leftEyeCenter[0]
angle = np.degrees(np.arctan2(dY, dX)) - 180
 
# поворачиваем изображение очков на вычисленный угол, чтобы
# поворот очков соответствовал наклону головы
sg = imutils.rotate_bound(sg, angle)
 
# очки не должны покрывать *всю* ширину лица, а в идеале
# только глаза — здесь выполняем примерную оценку и указываем
# 90% ширины лица в качестве ширины очков
sgW = int((endX - startX) * 0.9)
sg = imutils.resize(sg, width=sgW)
 
# в очках есть прозрачные части (нижняя часть, под линзами
# и носом), поэтому для получения приемлемого результата
# нужно применить маску и альфа-смешивание — здесь мы
# выполняем бинаризацию маски с той же обработкой, 
# как у очков выше
sgMask = cv2.cvtColor(sgMask, cv2.COLOR_BGR2GRAY)
sgMask = cv2.threshold(sgMask, 0, 255, cv2.THRESH_BINARY)[1]
sgMask = imutils.rotate_bound(sgMask, angle)
sgMask = imutils.resize(sgMask, width=sgW, inter=cv2.INTER_NEAREST)


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

Теперь можно повернуть и изменить размер очков. Обратите внимание, что мы используем функцию rotate_bound, а не просто rotate, чтобы OpenCV не обрезал части, которые не видны после аффинного преобразования.

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

Примечание: обратите внимание, что при изменении размера маски мы используем интерполяцию по ближайшим соседним точкам, потому что маска должна иметь только два значения (0 и 255). Другие методы интерполяции более эстетичны, но не подходят для маски. Здесь вы можете получить дополнительную информацию об интерполяции по ближайшим соседним точкам.

Оставшиеся три блока кода создают кадры для GIF-анимации:

# очки падают сверху кадра, так что 
# определяем N равных интервалов между верхним краем кадра
# и конечным положением
steps = np.linspace(0, rightEyeCenter[1], config["steps"],
        dtype="int")
 
# start looping over the steps
for (i, y) in enumerate(steps):
        # вычисляем значения небольшого смещения влево
        # и вверх, потому что очки *начинаются* не прямо в 
        # центре глаза, а это смещение позволяет покрыть всю
        # необходимую площадь
        shiftX = int(sg.shape[1] * 0.25)
        shiftY = int(sg.shape[0] * 0.35)
        y = max(0, y - shiftY)
 
        # add the sunglasses to the image
        output = overlay_image(image, sg, sgMask,
                (rightEyeCenter[0] - shiftX, y))


Очки падают с верхней части изображения. На каждом кадре они отображаются всё ближе лицу, пока не покроют глаза. С помощью переменной "steps" в конфигурационном файле JSON генерируем y-координаты для каждого кадра. Для этого без особых усилий используем функцию linspace из NumPy.

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

С помощью функции overlay_image генерируем итоговый кадр output.

Теперь наносим текст «DEAL WITH IT» с помощью другой маски:

      # если это последний шаг, то теперь добавляем 
        # текст "DEAL WITH IT" внизу кадра
        if i == len(steps) - 1:
                # загружаем картинку "DEAL WITH IT" и маску,
                # проверяем бинаризацию
                dwi = cv2.imread(config["deal_with_it"])
                dwiMask = cv2.imread(config["deal_with_it_mask"])
                dwiMask = cv2.cvtColor(dwiMask, cv2.COLOR_BGR2GRAY)
                dwiMask = cv2.threshold(dwiMask, 0, 255,
                        cv2.THRESH_BINARY)[1]
 
                # изменяем размер текста и маски на 80% ширины конечного
                # изображения
                oW = int(W * 0.8)
                dwi = imutils.resize(dwi, width=oW)
                dwiMask = imutils.resize(dwiMask, width=oW,
                        inter=cv2.INTER_NEAREST)
 
                # вычисляем координаты, где размещать текст, и 
                # добавляем его
                oX = int(W * 0.1)
                oY = int(H * 0.8)
                output = overlay_image(output, dwi, dwiMask, (oX, oY))


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

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

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

Осталось только сохранить каждый кадр на диск с последующим созданием GIF-анимации:

      # записать результат во временную папку
        p = os.path.sep.join([config["temp_dir"], "{}.jpg".format(
                str(i).zfill(8))])
        cv2.imwrite(p, output)
 
# все файлы уже записаны на диск, так что можно приступить
# к генерации GIF-анимации
print("[INFO] creating GIF...")
create_gif(config["temp_dir"], args["output"], config["delay"],
        config["final_delay"], config["loop"])
 
# очистка -- удаляем временную папку
print("[INFO] cleaning up...")
shutil.rmtree(config["temp_dir"], ignore_errors=True)


Записываем результат на диск. После генерации всех кадров вызываем функцию create_gif для создания файла GIF-анимации. Помните, что это оболочка, передающая параметры инструменту командной строки ImageMagick convert.

Наконец, удаляем временный выходной каталог и отдельные файлы изображений.

Результаты


Теперь самое интересное: посмотрим, что создал наш генератор мемов!

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

$ python create_gif.py --config config.json --image images/adrian.jpg \
        --output adrian_out.gif
[INFO] loading models...
[INFO] computing object detections...
[INFO] creating GIF...
[INFO] cleaning up...


dcab2446ec5dc96065e37a473375eea6.gif
Рисунок 9. GIF-анимация, сгенерированная с OpenCV и ImageMagick этим скриптом Python

Здесь вы можете увидеть GIF, созданный с помощью OpenCV и ImageMagick. На нём выполняются следующие действия:

  1. Правильное обнаружение моего лица.
  2. Локализация глаз и вычисление их центров.
  3. Очки правильно спадают на лицо.


Читатели моего блога знают, что я большой ботан по «Парку Юрского периода» и часто упоминаю его в своих книгах, курсах и учебных пособиях.

Не нравится «Парк Юрского периода»?

Хорошо, вот мой ответ:

$ python create_gif.py --config config.json --image images/adrian_jp.jpg \
        --output adrian_jp_out.gif
[INFO] loading models...
[INFO] computing object detections...
[INFO] creating GIF...
[INFO] cleaning up...


da90d7740e8ac813b9f99a727b9e04bc.gif
Рис. 10. GIF-анимация OpenCV на основе фотографии с недавнего показа фильма «Мир Юрского периода 2»

Здесь я на показе «Мира Юрского периода: 2» в тематической майке, со стаканом светлого и коллекционной книгой.

Весёлая история:

Пять или шесть лет назад мы с женой посетили тематический парк Epcot Center в Диснейуорлде, Флорида.

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

К сожалению, во Флориде всё время шёл дождь, а температура едва превышала 10° С.

Возле «Канадских садов» Триша меня сфотографировала: она говорит, что я похож на вампира с бледной кожей, тёмной одеждой и капюшоном, на фоне пышных садов позади:

$ python create_gif.py --config config.json --image images/vampire.jpg \
        --output vampire_out.gif
[INFO] loading models...
[INFO] computing object detections...
[INFO] creating GIF...
[INFO] cleaning up...


ad11ceca2ed48792a5aa28c0c8dc7f40.gif
Рис. 11. С помощью OpenCV и Python можно сделать этот мем или другой анимированный GIF

В тот же вечер Триша опубликовала фотографию в соцсетях — мне пришлось с этим смириться.

Те из вас, кто присутствовал на PyImageConf 2018 (читайте обзор), знают, что я всегда открыт для шуток. Вот например:

Вопрос: зачем петух переходит дорогу?

$ python create_gif.py --config config.json --image images/rooster.jpg \
        --output rooster_out.gif
[INFO] loading models...
[INFO] computing object detections...
[INFO] creating GIF...
[INFO] cleaning up...


5dde92ef04cb35436e87b0b70cc5926d.gif
Рис. 12. Лицо распознаётся даже при низком контрасте, а OpenCV корректно обрабатывает фотографию и опускает солнцезащитные очки

Ответ: Я не скажу ответ — смиритесь с этим.

Наконец, завершим сегодняшнее руководство добрым мемом.

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

Здесь вы можете увидеть Джемму у меня на плече:

$ python create_gif.py --config config.json --image images/pupper.jpg \
        --output pupper_out.gif
[INFO] loading models...
[INFO] computing object detections...
[INFO] creating GIF...
[INFO] cleaning up...


08ab5a728e8bd48ac2550a2962e76165.gif
Рис. 13. Джемма восхитительна. Ты так не думаешь? Тогда «смирись с этим»!

Не согласны, что она милая? Смиритесь с этим.

Появилась ошибка AttributeError?


Не волнуйтесь!

Если вы увидели такую ошибку:

$ python create_gif.py --config config.json --image images/adrian.jpg \
        --output adrian_out.gif
...
Traceback (most recent call last):
  File "create_gif.py", line 142, in 
    (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"]
AttributeError: module 'imutils.face_utils' has no attribute 'FACIAL_LANDMARKS_IDXS'


… то нужно просто обновить пакет imutils:

$ pip install --upgrade imutils
Collecting imutils
...
Successfully installed imutils-0.5.1


Почему?

По умолчанию imutils.face_utils использует 68-точечный детектор ориентиров, встроенный в dlib (как и в этой статье). Есть более быстрый 5-точечный детектор, который теперь тоже работает с imutils. Я недавно обновил imutils для поддержки обоих детекторов (поэтому вы можете увидеть ошибку).


В сегодняшнем уроке вы узнали, как создавать GIF с помощью OpenCV.

Чтобы сделать урок весёлым, мы использовали OpenCV для генерации GIF-анимации «Deal With It», популярного мема (и моего любимого), который в том или ином виде встречается почти на каждом сайте социальных сетей.

В процессе мы использовали компьютерное зрение и глубокое обучение для решения нескольких практических задач:

  • Определение лиц
  • Прогноз ориентиров на лице
  • Определение областей лица (в данном случае, глаз)
  • Вычисление угла между глазами для выравнивания лица
  • Создание прозрачных наложений с помощью альфа-смешивания


Наконец, мы взяли набор сгенерированных изображений и создали анимированный GIF с помощью OpenCV и ImageMagick.

Надеюсь, вам понравился сегодняшний урок!

Если понравилось, пожалуйста, оставьте комментарий и дайте мне знать.

Ну, а если тебе не понравилось, неважно, просто смирись с этим. ;)

© Habrahabr.ru