Не смотря на все прелести интернета, у него есть много минусов, и один из самых ужасных – это введения людей в заблуждение. Кликбейт, монтаж фотографий, ложные новости – все эти инструменты активно используются для обмана обычных пользователей в мировой сети, но в последние годы набирает обороты новый потенциально опасный инструмент, известный как DeepFake.
Меня данная технология заинтересовала недавно. Впервые о ней я узнал из доклада одного из спикеров на “AI Conference 2018”. Там демонстрировалось видео, в котором по аудиозаписи алгоритм сгенерировал видео с обращением Барака Обамы. Ссылка на подборку видео созданных с помощью этой технологии. Результаты меня сильно вдохновили, и мною было принято решение лучше разобраться с данной технологией, чтобы в будущем противодействовать ей. Для этого я решил написать DeepFake на языке C#. В итоге получил такой результат.

Приятного чтения!
Общие принципы
Отправной точкой стал этот проект. Из него я узнал как именно работает замена лица на видео.
Видео с демонстрацией работы проекта «FaceSwap»:
Работу я решил разбить на 3 части:
1-я) Замена лица на одном фото лицом с другого, без использования 3D маски
2-я) Доработка замены с применением 3D маски
3-я) Обработка видео
Замену лица на фото можно разложить на следующие пункты:
Встраивание одного изображения в другое
Первое с чего я начал работу — это встраивание одного изображения в другое. Для демонстрации встраивания в оригинальном проекте используется скрипт zad1.py.
В результате создается файл «eyeHandBlend.jpg», где глаз встраивается в руку.

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

k и n были подобраны эмпирически.
i — индекс пикселя по оси OX
j — индекс пикселя по оси OY
— компонента x центра изображения
— компонента y центра изображения
В итоге я получил следующий результат:

Поиск лица
Для поиска лица на фото существует множество алгоритмов:
Изначально использовался алгоритм Виолы-Джонса, но он оказался не достаточно точным, т.к. выделял лица не точно. Область выделения одного лица не совпадала с областью выделения второго, из-за чего замена происходила с дефектами, пример выделения лиц с помощь данного алгоритма показан ниже. Лица могут быть смещены, т.е. в на одном изображении оно захватывает оба уха, на другом только одно. Такие дефекты довольно плохо сказываются на конечном результате (на фото работа с DLib, предыдущая библиотека не всегда находила лицо, но к сожалению скриншоты не сохранились).

Далее я решил использовать Landmarks из библиотеки Dlib. Нашел DlibDotNet, который написан на .Net Core. Для использования в .Net Framework был создан промежуточный проект на .Net Standard 2.0 с основными функциями, поиска лица и выделения Landmarks.
После чего написал библиотеку на .Net Framework 4.6.1, в которой реализовал всю логику.
Пример получения Langmarks:

Лицо можно выделить точнее, находя самую левую, правую, верхнюю и нижнюю точки и строя рамки по ним.

Потом лицо вырезалось из картинки в правом нижнем углу и вставлялось, с помощью описанного выше алгоритма, в картину: «Caballero de la mano en el pecho».
Был получен следующий результат.

В следующей статье я планирую рассмотреть создание 3D маски по фотографии.
Меня данная технология заинтересовала недавно. Впервые о ней я узнал из доклада одного из спикеров на “AI Conference 2018”. Там демонстрировалось видео, в котором по аудиозаписи алгоритм сгенерировал видео с обращением Барака Обамы. Ссылка на подборку видео созданных с помощью этой технологии. Результаты меня сильно вдохновили, и мною было принято решение лучше разобраться с данной технологией, чтобы в будущем противодействовать ей. Для этого я решил написать DeepFake на языке C#. В итоге получил такой результат.

Приятного чтения!
Общие принципы
Отправной точкой стал этот проект. Из него я узнал как именно работает замена лица на видео.
- Загрузка картинки с которой мы будем брать лицо
- Извлечение лица
- Создание 3D маски
- Видео разбивается на кадры
- Вычисляется область локализации лица в кадре
- Вычисляется ракурс и выражение лица
- Перенос поворота и выражения лица на 3D модель
- Рендеринг
- Замена реального лица на кадре результатом рендеринга
Видео с демонстрацией работы проекта «FaceSwap»:
Работу я решил разбить на 3 части:
1-я) Замена лица на одном фото лицом с другого, без использования 3D маски
2-я) Доработка замены с применением 3D маски
3-я) Обработка видео
Замену лица на фото можно разложить на следующие пункты:
- Загрузка картинки с которой мы будем брать лицо
- Загрузка картинки на которую будем проецировать лицо
- Извлечение лиц
- Масштабирование лица взятого с изображения 2 к пропорциям в изображении 1
- Замена лица в картинке 1 на лицо в картинке 2
Встраивание одного изображения в другое
Первое с чего я начал работу — это встраивание одного изображения в другое. Для демонстрации встраивания в оригинальном проекте используется скрипт zad1.py.
В результате создается файл «eyeHandBlend.jpg», где глаз встраивается в руку.

Данный алгоритм состоит из 2х частей, первая переносит цвет из участка с лицом на исходной картинке, на то лицо, которое необходимо вставить. Вторая делает края картинки с нужным лицом прозрачными уменьшая прозрачность по мере приближения к центру изображения.
Первую часть я полностью перенес из оригинального проекта.
Код на питоне
def colorTransfer(src, dst, mask): transferredDst = np.copy(dst) #indeksy nie czarnych pikseli maski maskIndices = np.where(mask != 0) #src[maskIndices[0], maskIndices[1]] zwraca piksele w nie czarnym obszarze maski maskedSrc = src[maskIndices[0], maskIndices[1]].astype(np.int32) maskedDst = dst[maskIndices[0], maskIndices[1]].astype(np.int32) meanSrc = np.mean(maskedSrc, axis=0) meanDst = np.mean(maskedDst, axis=0) maskedDst = maskedDst - meanDst maskedDst = maskedDst + meanSrc maskedDst = np.clip(maskedDst, 0, 255) transferredDst[maskIndices[0], maskIndices[1]] = maskedDst return transferredDst
Код перенесенный на C#
static public Bitmap NewColor(Bitmap src, Bitmap ins, Rectangle r) { List<Vector> srV = new List<Vector>(); List<Vector> inV = new List<Vector>(); ; for (int i = r.X; i < r.X + r.Width-2; i+=3) { for (int j = r.Y; j < r.Y + r.Height-3; j+=4) { Color color = src.GetPixel(i, j); Color color2 = ins.GetPixel(i, j); srV.Add(new double[] { color.R, color.G, color.B }.ToVector()); inV.Add(new double[] { color2.R, color2.G, color2.B }.ToVector()); } } Vector meanSrc = Vector.Mean(srV.ToArray()) / 255; Vector meanInk = Vector.Mean(inV.ToArray()) / 255; Tensor tensor = ImgConverter.BmpToTensor (ins.Clone(r, PixelFormat.Format32bppArgb)); tensor = tensor.DivD(meanInk); tensor = tensor.PlusD(meanSrc); tensor = tensor.TransformTensor(x => { if (x < 0) x = 0; if (x > 1) x = 1; return x; }); return ImgConverter.TensorToBitmap(tensor); }
Чтобы края были более прозрачными, чем центральная часть изображения, для расчета альфа канала, была введена радиально-базисная функция следующего вида:
k и n были подобраны эмпирически.
i — индекс пикселя по оси OX
j — индекс пикселя по оси OY
В итоге я получил следующий результат:

Поиск лица
Для поиска лица на фото существует множество алгоритмов:
- Алгоритм Виолы-Джонса(каскады Хаара)
- Hog+SVM
- R-CNN
- Fast R-CNN
- Faster R-CNN
- Yolo
Изначально использовался алгоритм Виолы-Джонса, но он оказался не достаточно точным, т.к. выделял лица не точно. Область выделения одного лица не совпадала с областью выделения второго, из-за чего замена происходила с дефектами, пример выделения лиц с помощь данного алгоритма показан ниже. Лица могут быть смещены, т.е. в на одном изображении оно захватывает оба уха, на другом только одно. Такие дефекты довольно плохо сказываются на конечном результате (на фото работа с DLib, предыдущая библиотека не всегда находила лицо, но к сожалению скриншоты не сохранились).

Далее я решил использовать Landmarks из библиотеки Dlib. Нашел DlibDotNet, который написан на .Net Core. Для использования в .Net Framework был создан промежуточный проект на .Net Standard 2.0 с основными функциями, поиска лица и выделения Landmarks.
Код на C#
public int[] Face(byte[] bts, int row, int col, int st) { var img = Dlib.LoadImageData<RgbPixel> (ImagePixelFormat.Bgr, bts, (uint)row, (uint)col, (uint)st ); var face = faceDetector.Operator(img)[0]; int[] rect = { face.Left, face.Top, (int)face.Width, (int)face.Height}; return rect; } public List<int[]> FacePoints(byte[] bts, int row, int col, int st) { List<int[]> points = new List<int[]>(); var img = Dlib.LoadImageData<RgbPixel> (ImagePixelFormat.Bgr, bts, (uint)row, (uint)col, (uint)st); var face = faceDetector.Operator(img)[0]; var shape = shapePredictor.Detect(img, face); for (var i = 0; i < shape.Parts; i++) { var point = shape.GetPart((uint)i); points.Add(new int[] { point.X, point.Y }); } return points; }
После чего написал библиотеку на .Net Framework 4.6.1, в которой реализовал всю логику.
Пример получения Langmarks:

Лицо можно выделить точнее, находя самую левую, правую, верхнюю и нижнюю точки и строя рамки по ним.

Потом лицо вырезалось из картинки в правом нижнем углу и вставлялось, с помощью описанного выше алгоритма, в картину: «Caballero de la mano en el pecho».
Был получен следующий результат.

- Лица используемые для демонстрации алгоритмов взяты из этого проекта.
- Используемые библиотеки.
- Shape predictor(68 точек).
- Ссылка на проект.
В следующей статье я планирую рассмотреть создание 3D маски по фотографии.
