Не смотря на все прелести интернета, у него есть много минусов, и один из самых ужасных – это введения людей в заблуждение. Кликбейт, монтаж фотографий, ложные новости – все эти инструменты активно используются для обмана обычных пользователей в мировой сети, но в последние годы набирает обороты новый потенциально опасный инструмент, известный как DeepFake.
Меня данная технология заинтересовала недавно. Впервые о ней я узнал из доклада одного из спикеров на “AI Conference 2018”. Там демонстрировалось видео, в котором по аудиозаписи алгоритм сгенерировал видео с обращением Барака Обамы. Ссылка на подборку видео созданных с помощью этой технологии. Результаты меня сильно вдохновили, и мною было принято решение лучше разобраться с данной технологией, чтобы в будущем противодействовать ей. Для этого я решил написать DeepFake на языке C#. В итоге получил такой результат.
Приятного чтения!
Общие принципы
Отправной точкой стал этот проект. Из него я узнал как, именно, работает замена лица на видео.
1) Загрузка картинки с которой мы будем брать лицо
2) Извлечение лица
3) Создание 3D маски
4) Видео разбивается на кадры
5) Вычисляется область локализации лица в кадре
6) Вычисляется ракурс и выражение лица
7) Перенос поворота и выражения лица на 3D модель
8) Рендеринг
9) Замена реального лица на кадре результатом рендеринга
Видео с демонстрацией работы проекта «FaceSwap»:
Работу я решил разбить на 3 части:
1-я) Замена лица на одном фото лицом с другого, без использования 3D маски
2-я) Доработка замены с применением 3D маски
3-я) Обработка видео
Замену лица на фото можно разложить на следующие пункты:
1) Загрузка картинки с которой мы будем брать лицо
2) Загрузка картинки на которую будем проецировать лицо
3) Извлечение лиц
4) Масштабирование лица взятого с изображения 2 к пропорциям в изображении 1
5) Замена лица в картинке 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
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
— компонента x центра изображения
— компонента y центра изображения
В итоге я получил следующий результат:
Поиск лица
Для поиска лица на фото существует множество алгоритмов:
- Алгоритм Виолы-Джонса(каскады Хаара)
- Hog+SVM
- R-CNN
- Fast R-CNN
- Faster R-CNN
- Yolo
Изначально использовался алгоритм Виолы-Джонса, но он оказался не достаточно точным, т.к. выделял лица не точно. Область выделения одного лица не совпадала с областью выделения второго, из-за чего замена происходила с дефектами, пример выделения лиц с помощь данного алгоритма показан ниже. Лица могут быть смещены, т.е. в на одном изображении оно захватывает оба уха, на другом только одно. Такие дефекты довольно плохо сказываются на конечном результате (на фото работа с DLib, предыдущая библиотека не всегда находила лицо, но к сожалению скриншоты не сохранились).
Далее я решил использовать Landmarks из библиотеки Dlib. Нашел DlibDotNet, который написан на .Net Core. Для использования в .Net Framework был создан промежуточный проект на .Net Standard 2.0 с основными функциями, поиска лица и выделения Landmarks.
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 маски по фотографии.
Автор: Понимаш Захар Алексеевич